每隔一段时间,我遇到一个案例,我希望所有类的集合都拥有类似的逻辑。例如,我可能希望Bird
和Airplane
能够Fly()
。如果您正在考虑“策略模式”,我会同意,但即使采用策略,有时也无法避免重复代码。
例如,假设以下情况适用(这与我最近遇到的实际情况非常相似):
Bird
和Airplane
都需要包含实现IFlyBehavior
的对象的实例。Bird
和Airplane
在调用IFlyBehavior
时需要Fly()
个实例OnReadyToFly()
。Bird
和Airplane
在调用IFlyBehavior
时需要Land()
个实例OnReadyToLand()
。OnReadyToFly()
和OnReadyToLand()
是私有的。Bird
继承Animal
,Airplane
继承PeopleMover
。现在,让我们说我们稍后添加Moth
,HotAirBalloon
和其他16个对象,让我们说它们都遵循相同的模式。
我们现在需要20份以下代码:
private IFlyBehavior _flyBehavior;
private void OnReadyToFly()
{
_flyBehavior.Fly();
}
private void OnReadyToLand()
{
_flyBehavior.Land();
}
我不喜欢这两件事:
它不是很干(相同的九行代码一遍又一遍地重复)。如果我们发现了错误或向BankRight()
添加了IFlyBehavior
,我们就需要将更改传播到所有20个类。
没有任何方法可以强制所有20个类一致地实现这种重复的内部逻辑。我们不能使用接口,因为接口只允许公共成员。我们不能使用抽象基类,因为对象已经继承了基类,并且C#不允许多重继承(即使这些类还没有继承类,我们以后可能希望添加一个实现的新行为,比如ICrashable
,所以抽象基类并不总是一个可行的解决方案。)
如果......会怎样?
如果C#有一个新的构造,比如说pattern
或template
或[填写你的想法],它就像一个接口,但允许你在其上放置私有或受保护的访问修饰符,该怎么办?成员?您仍然需要为每个类提供一个实现,但如果您的类实现了PFlyable
模式,您至少可以强制执行每个类都有必要的样板代码来调用Fly()
和Land()
。而且,使用像Visual Studio这样的现代IDE,您可以使用“实现模式”命令自动生成代码。
就我个人而言,我认为只是扩展界面的含义以涵盖任何合同,无论是内部(私人/受保护)还是外部(公共)更有意义,但我建议首先添加一个全新的结构,因为人们似乎非常坚持“界面”这个词的含义,我不希望语义成为人们答案的焦点。
问题:
无论你怎么称呼它,我都想知道我在这里建议的功能是否有意义。我们是否需要一些方法来处理我们无法抽象出我们想要的代码的情况,因为需要限制性访问修饰符或者程序员无法控制的原因?
更新
根据AakashM的评论,我相信我要求的功能已有一个名称: Mixin 。所以,我想我的问题可以简化为:“C#应该允许Mixins吗?”
答案 0 :(得分:3)
您描述的问题可以使用访问者模式解决(一切都可以使用访问者模式解决,所以要小心!)
访问者模式允许您将实现逻辑移向新类。这样,您就不需要基类,并且访问者在不同的继承树上工作得非常好。
总结一下:
有关参考,请参阅Visitor pattern
答案 1 :(得分:2)
Visual Studio已经在“穷人形式”中提供了代码片段。此外,使用重构工具la ReSharper(甚至可能是Visual Studio中的本机重构支持),您可以在确保一致性方面获得很长的路要走。
[编辑:我没有想到扩展方法,这种方法更进一步(你只需要将_flyBehaviour保存为私有变量)。这使得我的答案的其余部分可能已经过时了......]
然而;只是为了讨论:如何改进?这是我的建议。
可以想象未来版本的C#编译器将支持以下内容:
// keyword 'pattern' marks the code as eligible for inclusion in other classes
pattern WithFlyBehaviour
{
private IFlyBehavior_flyBehavior;
private void OnReadyToFly()
{
_flyBehavior.Fly();
}
[patternmethod]
private void OnReadyToLand()
{
_flyBehavior.Land();
}
}
您可以使用以下内容:
// probably the attribute syntax can not be reused here, but you get the point
[UsePattern(FlyBehaviour)]
class FlyingAnimal
{
public void SetReadyToFly(bool ready)
{
_readyToFly = ready;
if (ready) OnReadyToFly(); // OnReadyToFly() callable, although not explicitly present in FlyingAnimal
}
}
这会有所改善吗?大概。是不是真的值得吗?也许...
答案 2 :(得分:2)
我们不能使用扩展方法来实现此目标
public static void OnReadyToFly(this IFlyBehavior flyBehavior)
{
_flyBehavior.Fly()
}
这模仿了您想要的功能(或Mixins)
答案 3 :(得分:1)
您刚才描述了aspect oriented programming。
C#的一个流行的AOP实现似乎是PostSharp(主网站似乎已关闭/不适用于我,this是直接“关于”页面。
跟进评论:我不确定PostSharp是否支持它,但我认为你在谈论AOP的this part:
类型间声明提供了一种方法 表达横切关注点 影响模块的结构。 这也称为开放类 使程序员能够在一个中声明 安置另一个人的成员或父母 类,通常是为了结合 与问题相关的所有代码 一方面。
答案 4 :(得分:0)
我将使用扩展方法来实现代码显示的行为。
让Bird
和Plane
个对象为接口IFlyBehavior
IFlyer
对象的属性
public interface IFlyer
{
public IFlyBehavior FlyBehavior
}
public Bird : IFlyer
{
public IFlyBehaviour FlyBehavior {get;set;}
}
public Airplane : IFlyer
{
public IFlyBehaviour FlyBehavior {get;set;}
}
为IFlyer
public IFlyerExtensions
{
public void OnReadyToFly(this IFlyer flyer)
{
flyer.FlyBehavior.Fly();
}
public void OnReadyToLand(this IFlyer flyer)
{
flyer.FlyBehavior.Land();
}
}
答案 5 :(得分:0)
你能通过在.NET 4.0中使用新的ExpandoObject来获得这种行为吗?
答案 6 :(得分:0)
Scala traits是为了解决这种情况而开发的。还有一些研究要包括traits in C#。
更新:我创建了自己的实验以获得roles in C#。看一看。