C#OOP组成和概括同时进行

时间:2014-06-30 07:47:12

标签: c# oop composition generalization

这可能是一个简单/基本的OOP问题,但我仍然无法弄清楚如何解决它。 在访谈中我遇到了以下问题:制作一个UML类图并编写一个智能手机的基本代码,其中包含电话和MP3播放器的功能。我们已经获得了以下(已接受)的解决方案:

class Telephone 
{
    public string name { get; set; }

    public Telephone()
    {
        name = "name telephone";
    }
}

class MP3 
{
    public string name { get; set; }

    public MP3()
    {
        name = "name mp3";
    }
}

" smart"手机课程:

class TelephoneMP3 
{
    public Telephone tel;
    public MP3 mp3;

    public TelephoneMP3()
    {
        tel = new Telephone();
        mp3 = new MP3();
    }
}

如您所见,我们在TelephoneMP3和Telephone / MP3类之间有一个组合关系。

但是,使用此代码,TelephoneMP3不是电话,而且TelephoneMP3也不是MP3,这是不合逻辑的。那么,为了使这个有效,我应该做些什么改变?例如,这种测试:

if (telMp3 is Telephone)
{
    Console.WriteLine("TelephoneMP3 is telephone");
}           
if (telMp3 is MP3)
{
    Console.WriteLine("TelephoneMP3 is mp3");
}

可以使用以下备注进行修改:

  1. 电话/ MP3 / TelephoneMP3必须保留课程(全部3个)
  2. 我可以在必要时添加接口/其他类
  3. TelephoneMP3不得复制电话/ MP3的所有功能(例如,在从有关所有界面的会员必须编写代码的接口的继承中继承)
  4. 提前谢谢

11 个答案:

答案 0 :(得分:35)

由于C#不支持多重继承,因此请考虑使用接口:

public interface Phone{ ... }
public interface Mp3{ ... }

public class Telephone : Phone{ ... }
public class Mp3Player : Mp3{ ... }
public class Smartphone : Phone, Mp3{ ... }

这种方式Smartphone同时为PhoneMp3。如果您需要编写一个在Telephone上运行的方法,请改用Phone接口。这样,您就可以传递TelephoneSmartphone作为参数。

答案 1 :(得分:18)

这里有一些很好的答案。说使用界面的答案是好的,这就是面试官可能正在寻找的。但是,我会考虑简单地否定这样一个前提,即“是一种”关系得到满足是一个好主意。相反,我会考虑使用服务提供商组织:

public interface ITelephone { ... }
internal class MyTelephone : ITelephone { ... }
public interface IMusicPlayer { ... }
internal class MyPlayer : IMusicPlayer { ... }
public interface IServiceProvider
{
  T QueryService<T>() where T : class;
}

internal class MyDevice : IServiceProvider
{
  MyTelephone phone = new MyTelephone();
  MyPlayer player = new MyPlayer();
  public T QueryService<T>() where T : class
  {
      if (typeof(T) == typeof(ITelephone)) return (T)(object)phone;
      if (typeof(T) == typeof(IPlayer)) return (T)(object)player;
      return null;
  }
}

现在,来电者通过其MyDevice界面手持IServiceProvider。你问它

ITelephone phone = myDevice.QueryService<ITelephone>();

如果phone非空,则设备可以像手机一样。但

myDevice is ITelephone

是假的。设备不是手机,它知道如何找到像手机一样的东西

有关此内容的更多信息,请研究插件架构,例如MAF。

答案 2 :(得分:17)

它与其他答案几乎相似,但是..
我认为它在继承层次结构方面具有最佳准确性。

internal class Program
{
    private static void Main(string[] args)
    {
        var telephone = new Telephone();
        Console.WriteLine(telephone.Name);
        telephone.OutboundCall("+1 234 567");
        Console.WriteLine("Am I a Telephone?                 {0}", telephone is Telephone);
        Console.WriteLine("Am I a MP3?                       {0}", telephone is MediaPlayer3);
        Console.WriteLine("Am I a Smartphone?                {0}", telephone is Smartphone);
        Console.WriteLine("Do I Have Telephone Capabilities? {0}", telephone is ITelephone);
        Console.WriteLine("Do I Have MP3 Capabilities?       {0}", telephone is IMediaPlayer3);
        Console.WriteLine();

        var mp3 = new MediaPlayer3();
        Console.WriteLine(mp3.Name);
        mp3.PlaySong("Lalala");
        Console.WriteLine("Am I a Telephone?                 {0}", mp3 is Telephone);
        Console.WriteLine("Am I a MP3?                       {0}", mp3 is MediaPlayer3);
        Console.WriteLine("Am I a Smartphone?                {0}", mp3 is Smartphone);
        Console.WriteLine("Do I Have Telephone Capabilities? {0}", mp3 is ITelephone);
        Console.WriteLine("Do I Have MP3 Capabilities?       {0}", mp3 is IMediaPlayer3);
        Console.WriteLine();

        var smartphone = new Smartphone();
        Console.WriteLine(smartphone.Name);
        smartphone.OutboundCall("+1 234 567");
        smartphone.PlaySong("Lalala");
        Console.WriteLine("Am I a Telephone?                 {0}", smartphone is Telephone);
        Console.WriteLine("Am I a MP3?                       {0}", smartphone is MediaPlayer3);
        Console.WriteLine("Am I a Smartphone?                {0}", smartphone is Smartphone);
        Console.WriteLine("Do I Have Telephone Capabilities? {0}", smartphone is ITelephone);
        Console.WriteLine("Do I Have MP3 Capabilities?       {0}", smartphone is IMediaPlayer3);

        Console.ReadKey();
    }

    public interface IDevice
    {
        string Name { get; }
    }

    public interface ITelephone : IDevice
    {
        void OutboundCall(string number);
    }

    public interface IMediaPlayer3 : IDevice
    {
        void PlaySong(string filename);
    }


    public class Telephone : ITelephone
    {
        public string Name { get { return "Telephone"; } }

        public void OutboundCall(string number)
        {
            Console.WriteLine("Calling {0}", number);
        }
    }

    public class MediaPlayer3 : IMediaPlayer3
    {
        public string Name { get { return "MP3"; } }

        public void PlaySong(string filename)
        {
            Console.WriteLine("Playing Song {0}", filename);
        }
    }

    public class Smartphone : ITelephone, IMediaPlayer3
    {
        private readonly Telephone telephone;
        private readonly MediaPlayer3 mp3;

        public Smartphone()
        {
            telephone = new Telephone();
            mp3 = new MediaPlayer3();
        }

        public string Name { get { return "Smartphone"; } }

        public void OutboundCall(string number)
        {
            telephone.OutboundCall(number);
        }

        public void PlaySong(string filename)
        {
            mp3.PlaySong(filename);
        }
    }

}

节目输出:

Telephone
Calling +1 234 567
Am I a Telephone?                 True
Am I a MP3?                       False
AM I a Smartphone?                False
Do I Have Telephone Capabilities? True
Do I Have MP3 Capabilities?       False

MP3
Playing Song Lalala
Am I a Telephone?                 False
Am I a MP3?                       True
AM I a Smartphone?                False
Do I Have Telephone Capabilities? False
Do I Have MP3 Capabilities?       True

Smartphone
Calling +1 234 567
Playing Song Lalala
Am I a Telephone?                 False
Am I a MP3?                       False
AM I a Smartphone?                True
Do I Have Telephone Capabilities? True
Do I Have MP3 Capabilities?       True

答案 3 :(得分:6)

我认为这个面试问题不是(应该是所有面试问题)关于挑战本身。通过作文合并两个班级的编码练习可以用教科书来回答。这个挑战是一个微妙的技巧问题,我建议重点是让你讨论为什么。至少这是我想从受访者那里得到的。


这个测试:

if(telMp3 is Telephone && telMp3 is MP3) {

......是真正的问题。你为什么必须符合这个标准?该测试完全阻止了从构图中构建对象的目的。它要求以特定方式实现对象。它表明现有的类实现已经与代码库紧密耦合(如果它们不能被废除)。这些要求意味着没有遵循SOLID principles,因为您不能只满足基类型的方法,您必须实际 基类型。这不好。


正如其他答案所说,解决方案是使用接口。然后,您可以将对象传递给需要接口的任何方法。这种用法需要进行类似的测试:

if (telMp3 is IPhone && telMp3 is IMp3) {

...但由于挑战的局限性,你无法做到这一点。这意味着在其余代码中,人们一直在编写明确依赖于特定类型TelephoneMP3的方法。这是真正的问题。


在我看来,这个挑战的正确答案是说代码库未通过测试。挑战中的具体影响是不可避免的;在正确解决之前,您需要更改挑战的要求。一个认识到这个事实的受访者会通过测试,并且有很多颜色。

答案 4 :(得分:5)

您也可以使用explicit interface implemenations来限制共享变量Name的使用。这样你就必须转向界面才能访问它。您仍然可以从界面获得公共属性/方法。

仍在使用合成,但SmartPhone可以控制其属性/方法的实现。

对我而言,这将是最容易实现的实现,因为我很少想使用两者来自mp3player和手机的实现,而是其中之一。此外,我仍然完全控制在SmartPhone上调用接口方法时会发生什么。

class User
{
    void UseSmartPhone(SmartPhone smartPhone)
    {
        // Cannot access private property 'Name' here
        Console.WriteLine(smartPhone.Name);

        // Cannot access explicit implementation of 'IMp3Player.Play'
        smartPhone.Play();

        // You can send the phone to the method that accepts an IMp3Player though
        PlaySong(smartPhone);

        // This works fine. You are sure to get the Phone name here.
        Console.WriteLine(((IPhone)smartPhone).Name);

        // This works fine, since the Call is public in SmartPhone.
        smartPhone.Call();
    }

    void CallSomeone(IPhone phone)
    {
        phone.Call();
    }

    void PlaySong(IMp3Player player)
    {
        player.Play();
    }
}

class SmartPhone : IPhone, IMp3Player
{
    private Phone mPhone;
    private Mp3Player mMp3Player;

    public SmartPhone()
    {
        mPhone = new Phone();
        mMp3Player = new Mp3Player();
    }

    public void Call()
    {
        mPhone.Call();
    }

    string IPhone.Name
    {
        get { return mPhone.Name; }
    }

    string IMp3Player.Name
    {
        get { return mMp3Player.Name; }
    }

    void IMp3Player.Play()
    {
        mMp3Player.Play();
    }
}

class Mp3Player
{
    public string Name { get; set; }

    public void Play()
    {
    }
}

class Phone
{
    public string Name { get; set; }

    public void Call()
    {
    }
}

interface IPhone
{
    string Name { get; }
    void Call();
}

interface IMp3Player
{
    string Name { get; }
    void Play();
}

答案 5 :(得分:2)

这个解决方案怎么样:

public interface ITelephone
{
    string Name{get;}
    void MakeCall();
}

public interface IMp3
{
    string Name { get; }
    void Play(string filename);
}

public abstract class BaseTelephone : ITelephone
{
    public virtual string Name { get { return "Telephone"; } }

    void MakeCall()
    {
        // code to make a call.
    }
}

public class MyMp3Player : IMp3
{
    public string Name { get { return "Mp3 Player"; } }

    public void Play(string filename)
    {
        // code to play an mp3 file.
    }
}

public class SmartPhone : BaseTelephone, IMp3
{
    public override string Name { get { return "SmartPhone"; } }

    private IMp3 Player { get { return _Player; } set { _Player = value; } }
    private IMp3 _Player = new MyMp3Player();

    public void Play(string filename) { Player.Play(filename); }
}

这样智能手机也可以是Mp3播放器,但在内部它有一个用于播放音乐的Mp3播放器。可以使用SmartPhone Player属性将内部播放器换成新的播放器(例如升级)。

手机的代码只能在基本手机课程中写入一次。 Mp3播放器的代码也只写一次 - 在MyMp3Player类中。

答案 6 :(得分:2)

使用strategy pattern(使用下面的一些快捷方式,您将获得要点)。

public class Device {

  private List<App> apps;

  public Device() {

    this.apps = new List<App>();

    this.apps.Add(new Mp3Player());
    this.apps.Add(new Telephone());

  }

}

public class Mp3Player implements App {...}
public class Telephone implements App {...}
public interface App {...}

免责声明:我的母语是PHP,原谅我任何非C#编码标准等等。

答案 7 :(得分:2)

您可以使用隐式投射

class TelephoneMP3 
{
    public Telephone tel;
    public MP3 mp3;

    public TelephoneMP3()
    {
        tel = new Telephone();
        mp3 = new MP3();
    }

    public static implicit operator Telephone(TelephoneMP3 telemp3) {
        return telemp3.tel;
    }

    public static implicit operator MP3(TelephoneMP3 telemp3) {
        return telemp3.mp3;
    }

}

它不会通过您提议的确切测试,但您可以

var teleMp3 = new TelephoneMP3();
Telephone t = teleMp3;

答案 8 :(得分:1)

您正在尝试对产品层次结构进行建模,其中给定产品可能具有自己的特定属性,以及由标准子产品组成。这确实是构图模式的一个例子。我建议为任何产品组件引入基础接口,然后为电话,MP3播放器和智能手机产品创建特定接口。

在传统的合成模式中,每个节点可以包含可以添加或删除子组件的任意组件列表,但是在您的数据模型中,对于每种特定类型的产品来说,指定其精确子项更有用,然后提供迭代它们的通用方法。这允许指定类型/接口的特定(子)组件在整个产品层次结构中轻松可查询

我还推出了GPS产品接口,因为所有新手机都包含内置GPS接收器 - 只是为了说明如何使用递归的组件层次结构。

public interface IProductComponent
{
    string Name { get; set; }

    IEnumerable<IProductComponent> ChildComponents { get; }

    IEnumerable<IProductComponent> WalkAllComponents { get; }

    TProductComponent UniqueProductComponent<TProductComponent>() where TProductComponent : class, IProductComponent;
}

public interface ITelephone : IProductComponent
{
    IGps Gps { get; }
}

public interface IMp3Player : IProductComponent
{
}


public interface IGps : IProductComponent
{
    double AltitudeAccuracy { get; }
}

public interface ISmartPhone : IProductComponent
{
    ITelephone Telephone { get; }

    IMp3Player Mp3Player { get; }
}

然后可以通过一组并行的类来实现这些接口:

public abstract class ProductComponentBase : IProductComponent
{
    string name;

    protected ProductComponentBase(string name)
    {
        this.name = name;
    }

    #region IProductComponent Members

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    public virtual IEnumerable<IProductComponent> ChildComponents
    {
        get
        {
            return Enumerable.Empty<IProductComponent>();
        }
    }

    public IEnumerable<IProductComponent> WalkAllComponents
    {
        get
        {
            yield return this;
            foreach (var child in ChildComponents)
            {
                foreach (var subChild in child.WalkAllComponents)
                    yield return subChild;
            }
        }
    }

    public TProductComponent UniqueProductComponent<TProductComponent>() where TProductComponent : class, IProductComponent
    {
        TProductComponent foundComponent = null;
        foreach (var child in WalkAllComponents.OfType<TProductComponent>())
        {
            if (foundComponent == null)
                foundComponent = child;
            else
                throw new Exception("Duplicate products found of type " + typeof(TProductComponent).Name);
        }
        return foundComponent;
    }

    #endregion
}

public class Telephone : ProductComponentBase, ITelephone
{
    IGps gps = new Gps();

    public Telephone()
        : base("telephone")
    {
    }

    #region ITelephone Members

    public IGps Gps
    {
        get
        {
            return gps;
        }
    }

    #endregion

    IEnumerable<IProductComponent> BaseChildComponents
    {
        get
        {
            return base.ChildComponents;
        }
    }

    public override IEnumerable<IProductComponent> ChildComponents
    {
        get
        {
            if (Gps != null)
                yield return Gps;
            foreach (var child in BaseChildComponents)
                yield return child;
        }
    }
}

public class Gps : ProductComponentBase, IGps
{
    public Gps()
        : base("gps")
    {
    }

    #region IGps Members

    public double AltitudeAccuracy
    {
        get { return 100.0; }
    }

    #endregion
}

public class TelephoneMP3 : ProductComponentBase, ISmartPhone
{
    ITelephone telephone;
    IMp3Player mp3Player;

    public TelephoneMP3()
        : base("TelephoneMP3")
    {
        this.telephone = new Telephone();
        this.mp3Player = new MP3();
    }

    IEnumerable<IProductComponent> BaseChildComponents
    {
        get
        {
            return base.ChildComponents;
        }
    }

    public override IEnumerable<IProductComponent> ChildComponents
    {
        get
        {
            if (Telephone != null)
                yield return Telephone;
            if (Mp3Player != null)
                yield return Mp3Player;
            foreach (var child in BaseChildComponents)
                yield return child;

        }
    }

    #region ISmartPhone Members

    public ITelephone Telephone
    {
        get { return telephone; }
    }

    public IMp3Player Mp3Player
    {
        get { return mp3Player; }
    }

    #endregion
}

public class MP3 : ProductComponentBase, IMp3Player
{
    public MP3()
        : base("mp3Player")
    {
    }
}

随着新产品组件类型的添加(或子类化),它们会覆盖&#34; ChildComponents&#34;他们的父母并返回他们特定领域的孩子。

完成此操作后,您可以(递归地)查询产品层次结构,以查找给定类型的组件供您使用。例如:

var accuracy = smartPhone.UniqueProductComponent<IGps>().AltitudeAccuracy

bool hasPhone = (component.UniqueProductComponent<ITelephone>() != null)

这种泛化和组合的组合避免了重复代码,同时明确了应该在任何给定产品中找到的子组件的类型。它还避免了让所有更高级别的产品代理其标准子代接口,将所有调用传递给它们的负担。

答案 9 :(得分:1)

与所有其他答案相反,我非常有信心问这个问题的方式使其无法实现。原因如下:

您明确说明

  

但是,使用此代码,TelephoneMP3不是电话,而且TelephoneMP3也不是MP3,这是不合逻辑的。那么,为了使这个有效,我应该做些什么改变?

看到&#34;这个词是&#34;让我立刻想到&#34;是&#34;运营商。我立即假设这是你真正想要的。

然后你继续说下面话:

  

电话/ MP3 / TelephoneMP3必须保留课程(全部3个)

确定我们可以做到以下几点:

interface ITelephone { }
class Telephone
{
    public string name { get; set; }

    public Telephone()
    {
        name = "name telephone";
    }
}
interface IMP3 { }

class MP3 : IMP3
{
    public string name { get; set; }

    public MP3()
    {
        name = "name mp3";
    }
}

class TelephoneMP3 : ITelephone, IMP3
{
    public Telephone tel;
    public MP3 mp3;

    public TelephoneMP3()
    {
        tel = new Telephone();
        mp3 = new MP3();
    }
}

但我们还有一个问题。 &#34;这个词是&#34;。由于我们必须保持班级TelephoneMP3,电话和MP3以及C#不支持多重继承,因此根本不可能。

说明我的观点:

public class Program
{
    static void Main(string[] args)
    {
        TelephoneMP3 t = new TelephoneMP3();
        Console.WriteLine((t is TelephoneMP3)? true:false);
        Console.WriteLine((t is ITelephone) ? true : false);
        Console.WriteLine((t is IMP3) ? true : false);
        Console.WriteLine((t is Telephone) ? true : false);
        Console.WriteLine((t is MP3) ? true : false);
        Console.ReadLine();
    }
}

这会给你

  

是的

     

是的

     

是的

     

     

换句话说,TelephoneMP3&#34;是&#34;一个ITelephone。 TelephoneMP3&#34;是&#34; IMP3;但是,TelephoneMP3不可能同时是MP3和电话。

答案 10 :(得分:-5)

C#不支持多重继承,你需要使用接口和抽象类来实现常见的实现,你可以这样做:

编辑:我在答案中添加了更多详细信息

abstract class BaseDevice 
{
    public string name { get; set; }

    public void Print()
    {
        Console.WriteLine("{0}", name );
    }
}

public interface IPhone
{
   void DoPhone();
}


public interface IMP3
{
    void DoMP3();
}

class Telephone :BaseDevice , IPhone
 {
     public Telephone()
     {
          name = "name telephone";
     }
 }

 class MP3 : BaseDevice , IMP3
 {
      public MP3()
      {
          name = "name mp3";
      }
 }

class telMp3 : BaseDevice , IMP3, IPhone
{
     private Telephone _tel;
     private MP3 _mp3; 

     public telMp3()
      {
          name = "name telMp3";
      }
     public  void DoPhone()
     {
         _tel.DoPhone();
     }
     public  void DoMP3()
     {
         _mp3.DoMP3();
     }


}
相关问题