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

Ion*_*onR 17 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";
    }
}
Run Code Online (Sandbox Code Playgroud)

而"智能"手机类:

class TelephoneMP3 
{
    public Telephone tel;
    public MP3 mp3;

    public TelephoneMP3()
    {
        tel = new Telephone();
        mp3 = new MP3();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

if (telMp3 is Telephone)
{
    Console.WriteLine("TelephoneMP3 is telephone");
}           
if (telMp3 is MP3)
{
    Console.WriteLine("TelephoneMP3 is mp3");
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 电话/ MP3 /电话MP3必须保持上课(所有3个)
  2. 如有必要,我可以添加接口/其他类
  3. (从一个接口继承时,例如在TelephoneMP3将不得不从该接口的所有成员编写的代码)TelephoneMP3不得复制所有从电话/ MP3的功能

先感谢您

Bar*_*zek 35

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

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

public class Telephone : Phone{ ... }
public class Mp3Player : Mp3{ ... }
public class Smartphone : Phone, Mp3{ ... }
Run Code Online (Sandbox Code Playgroud)

这种方式SmartphonePhoneMp3.如果您需要编写一个对a进行操作的方法Telephone,请改用该Phone接口.这样你就可以传递任何一个TelephoneSmartphone作为一个参数.

  • @Default,但使用"IPhone"存在法律后果.;) (27认同)
  • 请注意,C#中用于接口的通用命名标准是使用前缀"I". (19认同)

Eri*_*ert 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,呼叫者可以MyDevice通过其IServiceProvider界面进行操作.你问它

ITelephone phone = myDevice.QueryService<ITelephone>();
Run Code Online (Sandbox Code Playgroud)

如果phone是非null,则设备可以像手机一样工作.但

myDevice is ITelephone
Run Code Online (Sandbox Code Playgroud)

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

有关此内容的更多信息,请研究MAF等插件架构.

  • @adrianm:我承认这在某种意义上是滥用通用机制; 一个人更喜欢通用方法*通用*.也就是说,能够同样好地处理所有类型.我过去曾多次说过,如果你在泛型类型参数上进行类型测试,那么你可能做错了什么.这可能违反该准则.另一种方法是让方法采用`Type`并返回`object`,这似乎不太好. (2认同)

G.Y*_*G.Y 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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

节目输出:

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


Set*_*tin 6

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


这个测试:

if(telMp3 is Telephone && telMp3 is MP3) {
Run Code Online (Sandbox Code Playgroud)

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


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

if (telMp3 is IPhone && telMp3 is IMp3) {
Run Code Online (Sandbox Code Playgroud)

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


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


Def*_*ult 5

您也可以使用显式接口实现来限制共享变量的使用Name.这样你就必须转向界面才能访问它.您仍然可以从界面获得公共属性/方法.

仍然使用组合,但是SmartPhone可以控制其属性/方法的实现.

对我来说,这将是一起工作的最简单的实现,因为我很少想使用这两种从MP3播放器及手机执行,但他们中的相当一个.此外,我仍然可以完全控制在调用接口方法时发生的情况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();
}
Run Code Online (Sandbox Code Playgroud)


Sha*_*ler -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();
     }


}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案如何被接受?Telephone 和 Mp3 有一个共同的基类,该基类有一个 name 属性,这是如何正确继承的?Mp3 和 Telephone 并非源自同一个父对象。一种是“电话”,一种是“玩家”。IMp3 和 iPhone 都是空接口,只是为了通过“is”测试。我绝对不会在工作面试中接受这个答案。 (21认同)
  • 谢谢,但我对此不太确定,因为在这种情况下,我们将有一个电话是一个 MP3,而 MP3 是一个电话,这不应该是真的...... (2认同)
  • _in telMp3 对 IMP3 的呼叫应路由至 _mp3,对 IPhone 的呼叫应路由至 _tel_ 究竟是怎样的? (2认同)