【问题标题】:Concrete implementations of abstract method抽象方法的具体实现
【发布时间】:2016-12-22 23:59:19
【问题描述】:

我的目标是向客户端隐藏我正在构建的类的所有细节,包括要实例化的具体类。

客户端将枚举传递给工厂,该工厂返回适当派生类的实例,如下所示:

IAutomobile auto = Autofactory.GetAuto(param);

我创建了一个基类来完成大部分工作,但我需要特定的派生类来实现特定的逻辑。

public interface IAutomobile
{
    void Rename(string p1, int p2);
}
public abstract class Automobile : IAutomobile
{
    // Lots of properties and methods...

    public abstract void Rename(string p1, int p2);
}

public class Jeep : Automobile
{
    public override void Rename(string p1, int p2)
    {
        // Uses p1 & p2 with the base class
    }
}

现在客户端可以调用:

auto.Rename("s1", "s2");

一切都很好,直到我创建了另一个派生类,它实现了具有不同签名的“Rename()”方法。

public class Audi : Automobile
{
    public override void Rename(int i, SomeOtherClass someOtherClass)
    {
        //Does something with params
    }
}

第二个实现有一个不同的签名,这并不奇怪会引发编译错误。

所以,我尝试使用这样的字典使参数传递更加“灵活”:

public interface IAutomobile
{
    void Rename(Dictionary<object, object> parameters);
}

客户:

public class client
{
    public void Client()
    {
        var parameters = new Dictionary<object, object>();
        IAutomobile auto = AutoFactory.GetFactory();

        parameters.Add("Param1Name", "string1");
        parameters.Add("Param2Name", 21);
        auto.Rename(parameters);
    }
}

派生类:

public class Audi : Automobile, IAutomobile
{
    public override void Rename(Dictionary<object, object> parameters)
    {
        //  cast/box/unbox from dictionary objects...
        //  does something unique for Audis...
    }
} 

这就是我使用字典实现它的方式,其中参数是键/值对,但这似乎糟糕。我不明白它是如何给我买任何东西的。

或者,我可以创建代表所有派生类参数联合的对象,但同样,这看起来很糟糕。

有人可以建议我安排这种方式,以便不需要铸造、装箱和拆箱吗?

我不介意将要与 Rename() 方法一起使用的对象公开,例如:

auto.Rename(AudiFeatures);

auto.Rename(JeepFeatures);

(顺便说一句,我真的不想传递单个参数,我想传递对象)

这样的客户端调用不会起作用,因为我必须在客户端代码中硬编码类型。我想在工厂中对其进行硬编码,但是我将如何返回正确的接口类型,以及工厂的返回类型是什么?

我不想这样做:

IAutomobile<JeepRenamingData> auto = Autofactory.GetAuto<JeepRenamingData>(param);

我想这样做:

IAutomobile auto = Autofactory.GetAuto(param);
auto.Rename(myJeepRenamingData);

并让工厂返回正确的类型:

    public static IAuto GetAutomobileManager(AutoMaker manufacturer)
    {
        switch (manufacturer)
        {
            case AutoMaker.Jeep
                return new Jeep<JeepNamingOptions>();

            case AutoMaker.Audi
                return new Audi<AudiNamingOptions>();
        }

    }

【问题讨论】:

  • 说我这样做:IAutomobile auto = Autofactory.GetAuto(param);。我不知道auto 是什么,那我怎么知道我应该传入AudiFeatures 还是JeepFeatures?如果我确实知道auto 是什么,那么转换到接口和/或使用工厂有什么意义?如果Rename 更改了签名,则很好地表明它不适合覆盖方法的用例。
  • 如果用户确实知道要调用哪种类型的重命名,那么您可以创建一个具有不同签名的重命名函数。当然,用户必须将汽车转换为具体的类才能调用它。但是无论如何都需要知道什么样的汽车,才能知道要调用什么样的重命名,不是吗?
  • @Chris Berger,这组类支持 UI。该示例旨在为您省去所有血淋淋的细节;)但基本上用户指示基础汽车是奥迪或吉普,并且该选择的回调可以确定哪辆车。因此,它会被知道......我不想让 UI 知道派生类的细节......

标签: c# oop generics inheritance


【解决方案1】:

直接的想法是使用泛型。你能做到吗?

public interface IAutomobile { }

public interface IAutomobile<P1, P2> : IAutomobile
{
    void Rename(P1 p1, P2 p2);
}

public abstract class Automobile<P1, P2> : IAutomobile<P1, P2>
{
    // Lots of properties and methods...

    public abstract void Rename(P1 p1, P2 p2);
}

public class Jeep : Automobile<string, int>
{
    public override void Rename(string p1, int p2)
    {
        // Uses p1 & p2 with the base class
    }
}

客户端调用将如下所示:

IAutomobile<string, int> auto = Autofactory.GetAuto<string, int>(param);
auto.Rename("Foo", 42);
IAutomobile auto_plain = auto;

根据您的更新,这将是我现在的建议:

IAutomobile<JeepFeatures> auto = Autofactory.GetAuto<JeepFeatures>(param);
auto.Rename(new JeepFeatures());
IAutomobile auto_plain = auto;


public interface IAutomobile { }

public interface IAutomobile<P> : IAutomobile
{
    void Rename(P parameters);
}

public abstract class Automobile<P> : IAutomobile<P>
{
    public abstract void Rename(P parameters);
}

public class Jeep : Automobile<JeepFeatures>
{
    public override void Rename(JeepFeatures parameters)
    {
    }
}

【讨论】:

  • 您能否指出客户呼叫的外观?我没有得到那部分。客户端调用是否如下所示: IAutomobile auto = AutoFactory.GetFactory(param);
  • 好的,我已经从工厂传递了泛型类型信息,所以传递另一种类型是有意义的。我仍然不完全明白,但我会努力解决一段时间,然后发布我的结果。
  • @Dave 我有一种强烈的感觉,工厂是不是正确的方法。在上面的示例中使用它似乎没有任何好处。原因是: 1. 用户知道他们返回的是什么类型。 2. 工厂​​返回的对象没有实现单一接口。当你想说“给我一些保证做 X 的东西。我不在乎它是怎么做的,或者它是什么,只要它履行合同”。
  • @Rob 我同意你的观点,尤其是关于不实现单个接口。所以这是一种力量。但是对于 UI 完成的所有工作,使用单个对象会非常方便。可能有更好的模式选择,但这是我目标的一部分(设计更好的软件)
  • @Rob 我同意你的观点,尤其是关于不实现单个接口。所以这是一种力量。但是对于 UI 完成的所有工作,使用单个对象会非常方便。可能有更好的模式选择,但这是我目标的一部分(学习设计更好的软件)
【解决方案2】:

对我来说,无论哪种方式,您当前的方法都存在设计缺陷。

虽然您想完全隐藏具体的汽车细节,但您需要具体的细节,因为您无法使用您根本不了解的汽车...总之,我看到了过度抽象

与其想要像IAutomobile 这样过于抽象的接口来操纵你的汽车,你应该使用接口的力量从头开始解决你的问题:

public interface ICanRename<TArgs>
    where TArgs : class
{
    void Rename(TArgs args);
}

public interface IAutomobile 
{
    // Other common behaviors 
}

public interface IJeep : IAutomobile, ICanRename<JeepRenameArgs>
{
}

public class JeepRenameArgs {}

public class Jeep: IJeep
{
    public void Rename(JeepRenameArgs args)
    {
        // Rename stuff 
    }
}

public class AutomobileFactory
{
    private Dictionary<Type, Type> AutomobileImplementations = new Dictionary<Type, Type>
    {
        { typeof(IJeep), typeof(Jeep) } 
    };

    public TAutomobile Create<TAutomobile>() 
        where TAutomobile : IAutomobile
    {
        TAutomobile automobile = (TAutomobile)Activator.CreateInstance(AutomobileImplementations[typeof(TAutomobile)]);

        // Some initialization stuff here

        return automobile;
    }
}

所以你可以得到一个如下的汽车实现:

    AutomobileFactory automobileFactory = new AutomobileFactory();
    IJeep jeep = automobileFactory.Create<IJeep>();

    jeep.Rename(new JeepRenameArgs());

    // or even:
    ICanRename<JeepRenameArgs> jeep2 = automobileFactory.Create<IJeep>() as ICanRename<JeepRenameArgs>;
    jeep2.Rename(new JeepRenameArgs());

看看我如何在不知道实现或其接口的情况下重命名Jeep

无论如何...

看来您最终会重新发明轮子。上面的代码,甚至你的代码似乎都是控制容器服务定位器的基本反转。

使用Castle Windsor 之类的东西怎么样?

WindsorContainer container = new WindsorContainer();
container.Register
(
      Classes.FromThisAssembly().BasedOn<IAutomobile>()
                   .WithServiceAllInterfaces().LifestyleTransient()
);

IJeep jeep = container.Resolve<IJeep>();

或者,为了不作恶(你应该避免service locator anti-pattern),你应该利用dependency injection

public class YourClass
{
      public YourClass(IJeep jeep)
      {
          Jeep = jeep;
      }

      private IJeep Jeep { get; }

      public void DoStuff()
      {
           Jeep.Rename(new JeepArgs());
      }
}

...让控制容器的反转为您注入所需的汽车实现!

【讨论】:

    猜你喜欢
    • 2016-08-13
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多