C# 战略模式和;行动“;班级爆炸

C# 战略模式和;行动“;班级爆炸,c#,design-patterns,oop,C#,Design Patterns,Oop,有很多只做一件事的“工作”类(比如策略类)是不是很糟糕 让我们假设我想制作一个怪物类。我不只是在一个类中定义我想要的关于怪物的一切,我将尝试确定它的主要特性是什么,这样我就可以在接口中定义它们。这将允许: 如果我愿意的话,封班。稍后,其他用户可以创建一个新类,通过我定义的接口仍然具有多态性。我不必担心人们(或我自己)将来会如何更改/添加基类的特性。所有类都从对象继承,它们通过接口实现继承,而不是从母类实现继承 为我游戏世界的其他成员重复使用我与这个怪物一起使用的策略 缺点:这个模型很僵硬。有时,

有很多只做一件事的“工作”类(比如策略类)是不是很糟糕

让我们假设我想制作一个怪物类。我不只是在一个类中定义我想要的关于怪物的一切,我将尝试确定它的主要特性是什么,这样我就可以在接口中定义它们。这将允许:

  • 如果我愿意的话,封班。稍后,其他用户可以创建一个新类,通过我定义的接口仍然具有多态性。我不必担心人们(或我自己)将来会如何更改/添加基类的特性。所有类都从对象继承,它们通过接口实现继承,而不是从母类实现继承
  • 为我游戏世界的其他成员重复使用我与这个怪物一起使用的策略
  • 缺点:这个模型很僵硬。有时,我们想定义一些不容易实现的东西,而只是试图把这些“积木”组合在一起

    我的想法是接下来制定一系列不同的策略,供不同的怪物使用。另一方面,它们中的一些还可以用于完全不同的目的(即,我可以有一个也可以“游泳”的坦克)。我看到的这种方法的唯一问题是,它可能导致纯粹的“方法”类的爆炸,也就是说,作为唯一目的的策略类会执行这个或那个其他操作。另一方面,这种“模块化”将允许策略的高度重用,有时甚至在完全不同的上下文中

    你对这件事有什么看法?这是一个合理的理由吗?这是工程吗

    另外,假设我们对上面给出的示例进行了适当的调整,那么将IWalk定义为:

    interface IWalk {
        void Walk();
    }
    

    既然这样做了,我就不需要定义Monster本身的方法了,我只需要为IWalkStrategy提供公共getter(这似乎与您应该尽可能多地封装所有内容的想法背道而驰!) 为什么?


    感谢在支持多重继承的语言中,您确实可以通过继承表示它可以做的事情的类来创建您的
    怪物。在这些类中,您将为它编写代码,这样就不必在实现类之间复制代码

    在C#中,作为一种单一的继承语言,除了为它们创建接口之外,我看不到其他方法。如果类之间共享了大量代码,那么您的
    IWalkStrategy
    方法将很好地减少冗余代码。在您的示例中,您可能还希望将多个相关动作(例如行走、游泳和跑步)组合到一个类中

    重用和模块化是好东西,在我看来,在这种情况下,用几个方法拥有许多接口并不是一个真正的问题。特别是因为您用接口定义的操作实际上是不同的东西,可能会有不同的用法。例如,一个方法可能需要一个能够跳转的对象,因此它必须实现该接口。这样,您可以在编译时按类型强制执行此限制,而不是在运行时以其他方式在不满足方法期望时抛出异常

    简言之:我会按照你建议的方法来做,当它减少类之间的代码复制时,使用额外的
    I…策略
    方法

    “找出变化并封装它。”

    如果一个怪物走路的方式不同,那么将这种变化封装在一个抽象后面。如果你需要改变怪物走路的方式,你的问题中可能有一个状态模式。如果您需要确保行走和咆哮策略一致,那么您可能有一个抽象的工厂模式


    一般来说:不,将各种概念封装到它们自己的类中绝对不是过度工程。使具体类
    密封
    最终
    也没有错。它迫使人们在继承可能不应该继承的东西之前有意识地打破封装。

    行走、跑步和游泳似乎是实现而不是接口。您可以有一个ILocomation接口,并允许您的类接受ILocomation实现的列表

    Growl可以是一个类似于可维护性接口的实现。而且一个特定的怪物可能有一系列可维护性实现

    然后有两个接口,它们是使用哪种能力或运动的逻辑:例如IMove、IAct

    public class AlienMonster : IMove, IAct
    {
      private List<IAbility> _abilities;
      private List<ILocomotion> _locomotions;
    
      public AlienMonster()
      {
        _abilities = new List<IAbility>() {new Growl()};
        _locomotion = new List<ILocomotion>() {new Walk(), new Run(), new Swim()}
      }
    
      public void Move()
      {
        // implementation for the IMove interface
      }
    
      public void Act()
      {
        // implementation for the IAct interface
      }
    
    }
    

    上面的函数将接受任何实现IWalk的函数,但是如果IRun、ISwim等有一些函数的变体,那么您必须为它们中的每一个编写一个全新的函数,或者整体传递AlienMonster对象。如果传入该对象,则该函数可以调用该对象上的任何和所有接口。这还意味着该函数必须查询AlienMonster对象以查看其功能,然后决定使用哪个。所有这些最终都会产生许多外部功能,这些功能应该保持在类的内部。因为你将所有这些都外部化了,而IWalk、IRun和ISwim之间没有共同点,所以一些函数可能会天真地调用所有三个接口,而你的怪物可能会同时在行走和游泳。此外,由于您希望能够在某些类上调用IWalk、IRun、ISwim,因此所有类基本上都必须实现所有三个接口,并且您最终将制定一个类似CannotSwim的策略,以满足怪物不能游泳时ISwim的接口要求。否则,您可能会尝试调用一个未在怪物上实现的接口。最后,您实际上是在为额外的接口使代码变得更糟糕,依我看。

    从维护的角度来看,过度抽象可能与僵硬的单一代码一样糟糕。大多数抽象都添加了共谋
    interface IWalk {
        IWalkStrategy WalkStrategy { get; set; } //or something that ressembles this
    }
    
    public class AlienMonster : IMove, IAct
    {
      private List<IAbility> _abilities;
      private List<ILocomotion> _locomotions;
    
      public AlienMonster()
      {
        _abilities = new List<IAbility>() {new Growl()};
        _locomotion = new List<ILocomotion>() {new Walk(), new Run(), new Swim()}
      }
    
      public void Move()
      {
        // implementation for the IMove interface
      }
    
      public void Act()
      {
        // implementation for the IAct interface
      }
    
    }
    
    void SomeFunction(IWalk alienMonster) {...}