繁体   English   中英

委托方法或抽象类

[英]Delegate Method or Abstract Class

这是我的计划:

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)下载文件 - 起初我创建了单独的类,但后来我走了另一条路。

当谈到这些类型的设计选择时,我发现根据您尝试建模的问题域来考虑对象会有所帮助。 您已经将LoudFoo和QuietFoo显示为单个行为不同,但这是一个故意简化的示例。 在实际系统中,您可能有令人信服的理由将两个对象视为概念上不同的。

在前一版本中,SayHi是类行为的一个内在部分,如果该行为的性质以某种方式与其内部状态相互作用,这是恰当的。 也许SayHi的实现依赖于特定于派生类类型的对象的属性。

在后一版本中,SayingHi更像是一个可以分发给各种实例的工具。 当没有其他理由来区分不同类型的Foo实例时,这是合适的。

Stream是前一种模式的一个很好的例子,它提供的各种方法对于流操作的本质是固有的。 各种派生类将使用不同的状态来实现其方法。

Comparer是后一种模式的一个很好的例子,其中许多不同的对象类型想要使用比较的概念来操作。 除了想要使用此特定类型的行为之外,使用此功能的类不需要具有任何其他共同点。


关于提出这个问题的具体应用,那么多类方法又有什么尴尬? 如果存在冗余蔓延,则可能表明可以以更好地模拟问题的不同方式考虑责任。 如果不知道有关特定问题的其他详细信息,很难说更多,但很可能一个好的方法是你提出的两个方法的组合,一些单个类负责操作的排序和一个单独的层次结构(或一组接口实现) )实施特定于每项服务的操作。 本质上,接口(或基类)将分别传递的所有各种代理组合在一起。 这类似于StreamReader如何获取Stream并使用在Stream上运行的其他行为来增强它。

在设计1中,您的行为是在类中实现的,但在设计2中,您要求调用者定义行为。

我倾向于设计1,因为它使行为实现在类中保持黑盒子。 只要有人实例化一个新对象,设计2就可以改变你的实现。 我也不喜欢实现是调用者的责任。

如果您如何实现SayHi更改,您只需在设计1中更改一个地方,但如果您使用设计2,则可能在您的代码中有多个位置可以更改它。

根据经验:较少的代码==更易于维护。

在具体的情况下,你也有一个脱钩的设计 - 如何将SayHi与说出它的类分开的逻辑,让你可以选择组合行为。 低耦合也是通常要维护的代码的标志。

我倾向于第二种设计。

第一个设计更标准,逻辑是一致的(意味着任何其他使用LoudFoo (或QuietFoo )的类在任何地方都会有相同的结果。但是,它是可重用的,但仅限于其继承的路径。表示来自LoudFoo子类(比如DerivedLoudFoo不能使用QuietFoo定义的SayHi逻辑)。

这可能听起来很简单,但以后可能会很麻烦。 你可以在这里阅读我的答案,了解真实案例。

第二个是更可扩展的,但缺点是它可能有不同的行为。 不要将其用于核心业务流程(例如插入/更新/删除),因为它很难调试或修改。 但是,对于某些方法(如OnAfterInsertOnAfterSubmit ,最好在Framework级别中使用。

假设这不仅仅是一个完全构成的例子,而且实际上可以被翻译成真正的代码(我很难弄清楚它可能是什么),我发现选项2很糟糕。

  • 你几乎可以给SayHi分配任何东西,包括一个与Bar无关的lambda,这似乎不是你原来的意图。

  • 你基本上是想把一个精心制作的功能钉挂在一个好的旧的面向对象的洞里。 使用lambda将数据( Bar )与在其上运行的行为分开,这是有效的功能实践,但是通过使Foo.SayHi成为属性,您将回到OO样式,尝试将其中的两个封装回到同班。 似乎有点做作。

设计2困扰我,因为当我必须让Foo实际上是SayHi()时感觉多余。

如果将Foo类重新定义为

class Foo
    public property Bar as string
    public property SayHi as func(of string)
end class

然后可以使用闭包来创建和调用SayHi函数,而不将Foo作为函数参数传递:

dim Bar = "Felix"

dim Felix as new Foo with {
    .Bar = Bar,
    .SayHi = function() Bar.toLower
}

dim FelixSays = Felix.SayHi()

我倾向于设计1,因为它使行为实现在类中保持黑盒子。

设计2总是准备好进行黑盒行为实现,例如在工厂方法中:

function CreateQuietFoo(Bar as string) as Foo
    return new Foo with {
        .Bar = Bar,
        .SayHi = function() Bar.toLower
    }
end function

dim Felix = CreateQuietFoo("Felix")
dim Oscar = CreateQuietFoo("Oscar")

这样调用者不必提供SayHi方法来创建一个安静的Foo实例,他只是使用CreateQuietFoo工厂方法。

我的问题是使用Design 1还是Design 2--或者两者都不是更好。

如果您更喜欢Composition over Inheritance,请使用Design 2。 它使代码更灵活。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM