【问题标题】:Generating pass-through code when "preferring composition over inheritance"当“优先组合而不是继承”时生成传递代码
【发布时间】:2011-01-10 03:52:09
【问题描述】:

问题

假设我正在尝试将手机建模为普通手机和 PDA 的组合。这是一种多重继承场景(手机手机, PDA)。由于 C# 不支持多重继承,这几乎需要某种组合。另外,假设我有其他理由偏爱作曲。

我一直想知道的事情:是否有任何工具可以自动生成所有不可避免的直通代码?

让我用一些实际代码充实我的示例:

接口:

public interface IPhone
{
    public void MakeCall(int phoneNumber);
    public void AnswerCall();
    public void HangUp();
}

public interface IPda
{
    public void SendEmail(string[] recipientList, string subject, string message);
    public int LookUpContactPhoneNumber(string contactName);
    public void SyncWithComputer();
}

实现:

public class Phone : IPhone
{
    public void MakeCall(int phoneNumber) { // implementation }
    public void AnswerCall() { // implementation }
    public void HangUp() { // implementation }
}

public class Pda : IPda
{
    public void SendEmail(string[] recipientList, string subject, string message) { // implementation }
    public int LookUpContactPhoneNumber(string contactName) { // implementation }
    public void SyncWithComputer() { // implementation }
}

CellPhone 类

public class CellPhone : IPhone, IPda
{
    private IPhone _phone;
    private IPda _pda;

    public CellPhone(IPhone phone, IPda pda)
    {
        _phone = phone;
        _pda = pda;
    }

    public void MakeCall(int phoneNumber)
    {
        _phone.MakeCall(phoneNumber);
    }

    public void AnswerCall()
    {
        _phone.AnswerCall();
    }

    public void HangUp()
    {
        _phone.HangUp();
    }

    public void SendEmail(string[] recipientList, string subject, string message)
    {
        _pda.SendEmail(recipientList, subject, message);
    }

    public int LookUpContactPhoneNumber(string contactName)
    {
        return _pda.LookUpContactPhoneNumber(contactName);
    }

    public void SyncWithComputer()
    {
        _pda.SyncWithComputer();
    }
}

编写 CellPhone 类既乏味又容易出错:

这个类真正做的只是充当PhonePda 类的管道。确实没有理由需要人工输入所有这些传递语句(如_phone.MakeCall(phoneNumber);)。它只是暴露了几个成员字段的公共接口。

问题

  1. 有没有一种工具(最好是免费的 :))可以让我免于编写传递方法时容易出错的乏味?我知道我可以使用 VS 自动生成存根,但这只能让我完成一半。

  2. 您能否评价此类功能的可行性和可取性?是否值得向 Microsoft 提出添加此类代码生成的建议?如果我这样做,你会投票吗?如果没有,您有什么反对意见?

编辑

似乎每个人都在说同样的话:我为什么不把_phone_pda 变成公共属性?我对此的反对是,它违反了“最少知识原则”。我的CellPhone 课程的客户应该只做手机所做的事情,它不应该处理找出哪些功能是Phone 功能以及哪些是Pda 功能。这会产生额外的依赖关系,并使 CellPhone 界面的功能不那么明显。

另外,不要只关注这个例子。如果我正在编写一个适配器怎么办?它可能包含几个只是传递的接口成员,但它也可能包含一些具有唯一实现的唯一成员。我相信在很多情况下,传递代码是一个好东西,我只是不喜欢写它。

【问题讨论】:

  • 只是不明白为什么要再次公开这些相同的方法。为什么要注意只是添加方法,“getPhone”或“getPDA”,然后就可以了。除非你真的要在 CellPhone 类中有一些智能,在这种情况下,这个样板代码是无关紧要的。
  • @yar,这意味着客户端类必须参与 CellPhone 类的管道。 Client 不必知道CellPhone 实际上是PhonePda 的组合。它应该直接处理CellPhone 本身。这基本上是“知识最少的原则——只和你的直系朋友交谈。”
  • 是的,我明白了。我想客户没有理由知道电话或 PDA 上有什么。

标签: c# visual-studio design-patterns code-generation


【解决方案1】:

是的,您可以使用出色的 VS 插件 ReSharper 生成方法

从代码生成菜单中选择“generate delegating methods”选项(Alt-Insert 使用默认快捷方式)。

如果您有一个实现接口的类,其中包含实现相同接口的字段,R# 将为您提供生成传递代码的选项。它也可以与任意数量的接口一起使用。

【讨论】:

  • 感谢您的回答,马克,+1。我想这证明它确实是可行的:)
【解决方案2】:

我以前看过这个建议(不一定在 Stack Overflow 上)。那是不久前的事了。我相信作者创建了一个 Connect 问题,尽管我不知道它的状态。

建议的语法是以下之一:

public class CellPhone : IPhone, IPda
{
    private readonly IPhone _phone implements IPhone;
    private readonly IPda _pda : IPda;

    // ...
}

【讨论】:

  • 有趣,所以是一个实际的语言功能。我想过在我的问题中提出这样的建议,但我想得更好。如果您更改 iPhone 的定义,您可能会在不知不觉中破坏依赖于CellPhone 的类,因为传递方法没有显式存在。我承认这样的解决方案很诱人,因为它避免了太多样板文件的污染。
  • 更改接口会导致该问题无处不在 :-) 我敢肯定还会出现噩梦般的方法解析问题。不过看起来确实不错。
  • 随着 Roslyn 的出现,也许有一天会有人实现这个。
【解决方案3】:

为之前提供的答案添加一些视觉效果和细节。

  1. 将接口添加到您希望成为包装类的类中

    class MyWebElement : IWebElement { }
    

  1. 查找/单击“将“YourInterfaceHere”的实现委托给新字段

  1. 选择您的选项

  1. 点击完成,享受你的新课程

    class MyWebElement : IWebElement
    {
        private IWebElement _webElementImplementation;
        public IWebElement FindElement(By @by)
        {
            return _webElementImplementation.FindElement(@by);
        }
    
        public ReadOnlyCollection<IWebElement> FindElements(By @by)
        {
            return _webElementImplementation.FindElements(@by);
        }
    
        public void Clear()
        {
            _webElementImplementation.Clear();
        }
    
        public void SendKeys(string text)
        {
            _webElementImplementation.SendKeys(text);
        }
    
        public void Submit()
        {
            _webElementImplementation.Submit();
        }
    
        public void Click()
        {
            _webElementImplementation.Click();
        }
    
        public string GetAttribute(string attributeName)
        {
            return _webElementImplementation.GetAttribute(attributeName);
        }
    
        public string GetCssValue(string propertyName)
        {
            return _webElementImplementation.GetCssValue(propertyName);
        }
    
        public string TagName
        {
            get { return _webElementImplementation.TagName; }
        }
    
        public string Text
        {
            get { return _webElementImplementation.Text; }
        }
    
        public bool Enabled
        {
            get { return _webElementImplementation.Enabled; }
        }
    
        public bool Selected
        {
            get { return _webElementImplementation.Selected; }
        }
    
        public Point Location
        {
            get { return _webElementImplementation.Location; }
        }
    
        public Size Size
        {
            get { return _webElementImplementation.Size; }
        }
    
        public bool Displayed
        {
            get { return _webElementImplementation.Displayed; }
        }
    }
    

【讨论】:

  • 不过这需要 ReSharper。
【解决方案4】:

有趣的问题。 CellPhone 类真的可以依赖现有的具体 IPhone 类提供的 IPhone 方法的实现吗?还是 CellPhone 更有可能需要一些自定义代码来支持这些方法?

看着你的代码(虽然我不太了解你的要求)让我想写这样的东西:

public class CellPhone 
{
    private Phone _phone;
    private Pda _pda;

    public CellPhone(Phone phone, Pda pda)
    {
        _phone = phone;
        _pda = pda;
    }

    public IPhone getPhone()
    {
        return _phone;
    }

    public IPda getPda()
    {
        return _pda;
    }

    public void MakeCall(string contactName) {
        int phoneNumber = _pda.LookUpContactPhoneNumber(contactName);
        _phone.MakeCall(phoneNumber);
    }

}

问题 2:

我不太喜欢自动生成的代码。即使机器生成了它——并且没有错误——你仍然需要负责维护它。这些方法看起来很吵。

【讨论】:

  • 1.使用 getPhone() 强制客户端关心 CellPhone 是根据 Phone 和 PDA 实现的。 2. 每当您想使用电话或 PDA 方法时,都会产生额外的打字/句法噪音。 3. 手机就是手机,应该可以代替手机。现代手机也是 PDA,应该可以替代它。
  • 我对这些观点没有异议; OP 询问如何避免为 iPhone 和 IPda 方法编写“通过”代码,这是这样做的一种方法。在每种情况下甚至在这种情况下,它都可能不是一个好的选择。我可以补充一点,MakeCall() 和 SendEmail() 方法的繁重工作可能会更好地放置在与协议等相关的服务对象中,而不是直接放在 iPhone impls 中;这种方法意味着编写一个 IPhone impl 对你的影响很小,也许你不应该费心。在任何情况下,CellPhone 都应该提供 MakeCall(string contactName)。
  • 具体来说,我想避免手工编码传递代码,而不是消除传递代码。我同意你的观点,代码很嘈杂,但强制客户端处理 PdaPhone 对象也很嘈杂,这意味着它们依赖于三个类(CellPhonePdaPhone ) 而不是一个。如果我现在将MakeCall() 实现从Phone 移到Service 对象中,这将是一个重大更改。所有依赖CellPhone.Phone.MakeCall() 的对象都必须更改为CellPhone.Service.MakeCall(),这是我绝对想避免的。
  • @DanM 不,不移动 MakeCall() 方法本身,只是繁重的工作。想象一下,您正在实施一部通过“公共交换电话网络”(PSTN)拨打电话的电话;你可能有一个 PtsnServiceBroker 类,它知道如何 openPhoneLine()、sendDidget() 和 disconnect()。 MakeCall() 方法的实现将是对这些服务方法的一堆调用。那么你可能有一个不同的 IPhone impl,它对 PSTN 一无所知,而是通过 VoIP 拨打电话。
  • 我只是用你的例子来说明我的观点,但我想我错误地解释了你的例子:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多