这可能是一个简单/基本的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");
}
可以使用以下备注进行修改:
提前谢谢
答案 0 :(得分:35)
由于C#不支持多重继承,因此请考虑使用接口:
public interface Phone{ ... }
public interface Mp3{ ... }
public class Telephone : Phone{ ... }
public class Mp3Player : Mp3{ ... }
public class Smartphone : Phone, Mp3{ ... }
这种方式Smartphone
同时为Phone
和Mp3
。如果您需要编写一个在Telephone
上运行的方法,请改用Phone
接口。这样,您就可以传递Telephone
或Smartphone
作为参数。
答案 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) {
...但由于挑战的局限性,你无法做到这一点。这意味着在其余代码中,人们一直在编写明确依赖于特定类型Telephone
和MP3
的方法。这是真正的问题。
在我看来,这个挑战的正确答案是说代码库未通过测试。挑战中的具体影响是不可避免的;在正确解决之前,您需要更改挑战的要求。一个认识到这个事实的受访者会通过测试,并且有很多颜色。
答案 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();
}
}