这可能是一个简单/基本的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不是电话,而且TelephoneMP3也不是MP3,这是不合逻辑的.那么,为了使这个有效,我应该做些什么改变?例如,这种测试:
if (telMp3 is Telephone) { Console.WriteLine("TelephoneMP3 is telephone"); } if (telMp3 is MP3) { Console.WriteLine("TelephoneMP3 is mp3"); }
可以使用以下备注进行修改:
电话/ MP3 /电话MP3必须保持上课(所有3个)
如有必要,我可以添加接口/其他类
(从一个接口继承时,例如在TelephoneMP3将不得不从该接口的所有成员编写的代码)TelephoneMP3不得复制所有从电话/ MP3的功能
先感谢您
这里有一些很好的答案.说使用界面的答案是好的,这就是面试官可能正在寻找的.但是,我会考虑简单地否定这样一个前提,即"是一种"关系得到满足是一个好主意.相反,我会考虑使用服务提供商组织:
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
是非null,则设备可以像手机一样工作.但
myDevice is ITelephone
是假的.该设备不是手机,它知道如何找到像手机一样的东西.
有关此内容的更多信息,请研究MAF等插件架构.
它几乎与其他答案类似,但是......
我认为它在继承层次结构方面具有最佳准确性.
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
您也可以使用显式接口实现来限制共享变量的使用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(); }
由于C#不支持多重继承,因此请考虑使用接口:
public interface Phone{ ... } public interface Mp3{ ... } public class Telephone : Phone{ ... } public class Mp3Player : Mp3{ ... } public class Smartphone : Phone, Mp3{ ... }
这种方式Smartphone
是Phone
和Mp3
.如果您需要编写一个对a进行操作的方法Telephone
,请改用该Phone
接口.这样你就可以传递任何一个Telephone
或Smartphone
作为一个参数.
我认为这个面试问题不是(应该是所有面试问题)关于挑战本身.通过作文合并两个班级的编码练习可以用教科书来回答.这个挑战是一个微妙的技巧问题,我建议重点是让你讨论原因.至少这是我想从受访者那里得到的.
这个测试:
if(telMp3 is Telephone && telMp3 is MP3) {
......是真正的问题.你为什么必须符合这个标准?该测试完全阻止了从构图中构建对象的目的.它要求以特定方式实现对象.它表明现有的类实现已经与代码库紧密耦合(如果它们无法完成).这些要求意味着没有遵循SOLID原则,因为您不能只实现基类型的方法,您必须实际上是基类型.那不好.
正如其他答案所说,解决方案是使用接口.然后,您可以将对象传递给任何需要该接口的方法.这种用法需要像这样的测试:
if (telMp3 is IPhone && telMp3 is IMp3) {
......但由于挑战的局限性,你无法做到这一点.这意味着在其余的代码中,人们一直在编写明确依赖于特定类型Telephone
和方法的方法MP3
.这是真正的问题.
在我看来,这个挑战的正确答案是说代码库未通过测试.挑战中的具体影响是不可避免的; 在正确解决之前,您需要更改挑战的要求.一个认识到这个事实的受访者会通过测试,并且有很多颜色.