这是我的计划:
class Program
{
//DESIGN 1
abstract class AFoo
{
public string Bar { get; set; }
public abstract string SayHi();
}
class LoudFoo : AFoo
{
public override string SayHi()
{
return this.Bar.ToUpper();
}
}
class QuietFoo : AFoo
{
public override string SayHi() { return this.Bar.ToLower(); }
}
//DESIGN 2
class Foo{
public string Bar { get; set; }
public Func<Foo, string> SayHi { get; set; }
}
static void Main(string[] args)
{
//USING DESIGN 1
var quietFoo2 = new QuietFoo{ Bar = "Mariane"};
var loudFoo2 = new LoudFoo{ Bar = "Ginger"};
Console.WriteLine(quietFoo2.SayHi());
Console.WriteLine(loudFoo2.SayHi());
//USING DESIGN 2
var quietFoo = new Foo
{
Bar = "Felix",
SayHi = (f) => { return f.Bar.ToLower(); }
};
var loudFoo = new Foo
{
Bar = "Oscar",
SayHi = (f) => { return f.Bar.ToUpper(); }
};
Console.WriteLine(quietFoo.SayHi(quietFoo));
Console.WriteLine(loudFoo.SayHi(loudFoo));
}
}
我可以完成“同样的事情” - 实际上不是完全相同的事情,但类似的事情会走两条不同的路线。
设计1)我可以创建一个抽象类来强制该类的实现者如何使用SayHi()
- 或 -
设计2)我可以创建一个类来定义一个SayHi属性,它是一个函数。 (我称之为代表 - 但我不确定这是否是正确的术语)
设计1困扰我,因为它可能会导致课程的表现
尚未....
设计2困扰我,因为当我必须让Foo实际上是SayHi()时感觉多余。
felix.SayHi(felix)
我的问题是使用Design 1还是Design 2--或者两者都不是更好。当我说得更好时,我说的是能够维持我的程序更实用。当我创建不同的类时,我遇到了这个问题,这些类将用于从不同的云API(Google Drive,Box.com,DropBox)下载文件 - 起初我创建了单独的类,但后来我走了另一条路。 / p>
答案 0 :(得分:5)
当涉及到这些类型的设计选择时,我发现根据您尝试建模的问题域来考虑对象会有所帮助。您已经将LoudFoo和QuietFoo显示为单个行为不同,但这是一个故意简化的示例。在实际系统中,您可能有令人信服的理由认为两个对象在概念上是截然不同的。
在前一版本中,SayHi是类行为的一个内在部分,如果该行为的性质以某种方式与其内部状态相互作用,这是恰当的。也许SayHi的实现依赖于特定于派生类类型的对象的属性。
在后一版本中,SayingHi更像是一个可以分发给各种实例的工具。当没有其他理由可以区分不同类型的Foo实例时,这是合适的。
Stream是前一种模式的一个很好的例子,它提供的各种方法对于流操作的本质是固有的。各种派生类将使用不同的状态来实现它们的方法。
Comparer是后一种模式的一个很好的例子,其中许多不同的对象类型想要使用比较的概念来操作。除了想要使用这种特定类型的行为之外,使用此功能的类不需要具有任何其他共同点。
关于提出这个问题的具体应用,那么多类方法有何尴尬?如果存在冗余蔓延,则可能表明可以以更好地模拟问题的不同方式考虑责任。如果不知道有关特定问题的其他详细信息,很难说更多,但很可能一个好的方法是你提出的两个方法的组合,一些单个类负责操作的排序和一个单独的层次结构(或一组接口实现) )实施特定于每项服务的操作。本质上,接口(或基类)将分别传递的所有各种代理组合在一起。这类似于StreamReader如何获取Stream并使用在Stream上运行的其他行为来增强它。
答案 1 :(得分:4)
在设计1中,您的行为是在类中实现的,但在设计2中,您要求调用者定义行为。
我倾向于设计1,因为它使行为实现在类中保持黑盒子。只要有人实例化一个新对象,设计2就可以改变你的实现。我也不喜欢实现是调用者的责任。
如果您实施SayHi
更改的方式,您只需在设计1中更改一个地方,但如果您使用设计2,则可能在您的代码中有多个位置可以更改它。
答案 2 :(得分:2)
根据经验:较少的代码==更易于维护。
在具体情况下,您也有一个脱钩设计 - 如何将SayHi
与说出它的类分开的逻辑,让您可以选择撰写行为。低耦合也是通常要维护的代码的标志。
我的偏好是第二种设计。
答案 3 :(得分:1)
第一个设计更标准,逻辑是一致的(意味着使用LoudFoo
(或QuietFoo
)的任何其他类在任何地方都会有相同的结果。但是,它是可重复使用的,但仅限于在其继承的路径中。表示来自LoudFoo
的子类(说DerivedLoudFoo
)不能使用SayHi
中定义的QuietFoo
逻辑。
这可能听起来很简单,但以后可能会很麻烦。你可以在here阅读我的答案 对于现实案例。
第二种是更可扩展的,但缺点是它可能有不同的行为。不要将其用于核心业务流程(例如插入/更新/删除),因为它很难调试或修改。但是,对于某些方法(例如Framework
,OnAfterInsert
),最好在OnAfterSubmit
级别使用。
答案 4 :(得分:1)
假设这不仅仅是一个完全构成的例子,而且实际上可以翻译成真实的代码(我很难弄清楚它可能是什么),我发现选项2很糟糕。
你几乎可以为SayHi
指定任何内容,包括一个与Bar
无关的lambda,这似乎不是你最初的意图。
你基本上是想把一个精心制作的功能钉挂在一个好的旧的面向对象的洞里。使用lambda将数据(Bar
)与对其进行操作的行为分开,这是有效的功能实践,但是通过使Foo.SayHi
成为属性,您将回到OO样式,试图封装其中两个回到同一个班级。似乎有点做作。