17

这可能是一个简单/基本的 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";
    }
}

和“智能”电话类:

class TelephoneMP3 
{
    public Telephone tel;
    public MP3 mp3;

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

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

但是,使用此代码,TelephoneMP3 不是 Telephone,TelephoneMP3 也不是 MP3,这是不合逻辑的。那么,我应该进行哪些更改才能使其有效?例如,这种测试:

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

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

  1. Telephone / MP3 / TelephoneMP3 必须保留类(全部 3 个)
  2. 如有必要,我可以添加接口/其他类
  3. TelephoneMP3 不得复制 Telephone / MP3 的所有功能(例如,在从接口继承期间,TelephoneMP3 必须编写来自所有接口成员的代码)

先感谢您

4

11 回答 11

35

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

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

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

这种方式SmartphonePhoneMp3。如果您需要编写对 a 进行操作的方法Telephone,请改用Phone接口。这样你就可以传递Telephone或者Smartphone作为参数传递。

于 2014-06-30T08:09:45.473 回答
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 等插件架构。

于 2014-06-30T20:10:17.517 回答
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);
        }
    }

}

程序输出:

电话
致电 +1 234 567
我是电话吗?真的
我是MP3吗?错误的
我是智能手机吗?错误的
我有电话功能吗?真的
我有 MP3 功能吗?错误的

MP3
弹唱啦啦啦
我是电话吗?错误的
我是MP3吗?真的
我是智能手机吗?错误的
我有电话功能吗?错误的
我有 MP3 功能吗?真的

手机
致电 +1 234 567
弹唱啦啦啦
我是电话吗?错误的
我是MP3吗?错误的
我是智能手机吗?真的
我有电话功能吗?真的
我有 MP3 功能吗?真的
于 2014-06-30T09:42:16.683 回答
6

我认为这个面试问题(所有面试问题都应该如此)与挑战本身无关。通过组合合并两个类的编码练习可以用教科书来回答。这个挑战是一个微妙的技巧问题,我建议重点是让你讨论为什么。至少这是我希望我的受访者提供的。


本次测试:

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

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


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

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

...但你不能这样做,因为你的挑战有限。这意味着在您的其余代码中,人们一直在编写显式依赖于特定类型TelephoneMP3. 这才是真正的问题。


在我看来,这个挑战的正确答案是说代码库没有通过测试。你的挑战的具体后果是无关紧要的;您需要更改挑战的要求,然后才能正确解决它。承认这一事实的受访者会以优异的成绩通过测试。

于 2014-06-30T16:20:46.213 回答
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();
}
于 2014-06-30T08:52:15.810 回答
2

使用策略模式(使用下面的一些快捷方式,您将了解要点)。

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# 编码标准等。

于 2014-06-30T12:59:09.833 回答
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 播放器。Player可以使用 SmartPhone属性将内部播放器更换为新播放器(例如升级) 。

电话的代码只在电话基类中编写一次。Mp3 播放器的代码也只编写一次 - 在 MyMp3Player 类中。

于 2014-06-30T08:22:09.153 回答
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;
于 2014-07-01T11:28:50.470 回答
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")
    {
    }
}

随着新产品组件类型的添加(或子类化),它们将覆盖其父级的“ChildComponents”并返回其特定于域的子级。

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

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

或者

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

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

于 2014-06-30T19:10:22.503 回答
1

与所有其他答案相反,我非常有信心提出这个问题的方式使它成为不可能。原因如下:

您明确声明

但是,使用此代码,TelephoneMP3 不是 Telephone,TelephoneMP3 也不是 MP3,这是不合逻辑的。那么,我应该进行哪些更改才能使其有效?

看到“is”这个词让我立刻想到了“is”运算符。我立即假设这是您真正想要的。

然后,您稍后继续说以下内容:

Telephone / 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();
    }
}

但是我们还有一个问题。“是”这个词。由于我们必须保留 TelephoneMP3、Telephone 和 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“是”一个 ITelephone。TelephoneMP3“是”一个 IMP3;但是,TelephoneMP3 不可能既是 MP3 又是电话。

于 2014-07-04T20:59:47.200 回答
-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();
     }


}
于 2014-06-30T07:55:11.077 回答