如何最好地设计可扩展的类?

时间:2010-07-07 15:05:27

标签: c# architecture class-design

我的意思是: 我现在基本上有一个具有太多属性和功能的类。为了保持高效和易懂,它需要以某种方式缩小。但我仍然需要所有这些属性和方法。 就像这样:

class Apple

  float seedCount;
  ...
  ...about 25 variables and properties here.
  void Update() <-- a huge method that checks for each property and updates if so

在大多数情况下,该类几乎不需要这些属性。在某些情况下,需要能够非常有选择地增长并获得功能或丢失功能。 我提出的唯一解决方案是,我创建了一堆类并在那里放置了一些属性。我只在需要其中一个属性时初始化这个类对象,否则它保持为空。

class Apple

  Seed seed;

因此有很多问题: 我经常需要检查每个对象并确定每个帧的特征。如果种子没有初始化,我不必为它计算任何东西。如果是的话,我必须这样做。 如果我决定将多个属性/特性放入Seed类中,我还需要检查每一个属性/特征。 它变得越来越复杂。因此,我遇到的问题是,我需要对所有功能进行精细控制,并且不能将它们智能地拆分为更大的子类。任何形式的子类都只包含一堆需要检查和更新的属性。 我不能完全创建Apple的子类,因为需要这种高粒度控制。创建与属性组合一样多的类将是疯狂的。 我的主要目标:我想要短代码。

8 个答案:

答案 0 :(得分:5)

  

创建与属性组合一样多的类将是疯狂的。

听起来你可能正在寻找Decorator Pattern.它的目的是让管理具有许多不同属性组合的对象变得更容易,而不会出现指数增长的层次结构。每个属性或行为只有一个小子类(不一定是一个C#属性,只是你可以组合在一起的东西),然后你可以在运行时将它们组合在一起。

在您的情况下,每个Apple装饰器类将覆盖您的Update方法,并对其各个部分进行必要的计算,然后调用base.Update将其传递给下一行。

您的最终答案在很大程度上取决于您的“Apple”究竟是什么。

答案 1 :(得分:2)

在我的other answer中查看您的评论和示例之后,我已经考虑过Decorator模式以及它如何被使用以及您希望如何工作。我得出结论,装饰师不适合这个目的。我在想战略。我已经修改了之前的sample code以供您查看。

我完全摆脱了装饰者。 Broodfather抽象类仍然存在。它还有两个属性:IBroodfatherMagicAbility和IBroodfatherBloodthirstAbility。这两个属性将允许您访问与这些功能相关的不同属性,但是这一切的关键是实现功能的策略可以在运行时更改(请参阅Strategy pattern)。

每个阶段都有两个实施血战和魔法的“策略”。

  • IBroodfatherBloodthirstAbility.cs - 这是所有“嗜血战略”必须实施的界面。
  • BroodfatherNonBloodThristy.cs - 实现非嗜血属性的类。
  • BroodfatherBloodThristy.cs - 实现嗜血属性的类。

  • IBroodfatherMagicAbility.cs - 这是所有“神奇策略”必须实现的界面。

  • BroodfatherNonMagical.cs - 实现非魔法策略的类。
  • BroodfatherMagical.cs - 实现魔法战略的类。

  • BasicBroodfather.cs - 这与前面的示例类似,不同之处在于现在创建实例时,魔法和血腥属性被设置为非魔法和非血腥策略对象的新实例。

  • Program.cs是一个驱动程序,它显示了这些类以及如何在运行时交换和转换不同的策略。

我认为您会发现更适合您希望工作的方式。

答案 2 :(得分:1)

您可以在Apple类中使用嵌套类 http://msdn.microsoft.com/en-us/library/ms173120(VS.80).aspx

答案 3 :(得分:1)

我认为这里的关键是你试图把所有东西放在一个班级里。因此,班级必须不断检查它有什么,不做什么。解决方案是创建已经知道它们是否具有特定事物的子类或装饰器。然后他们不必每次都检查它。

因为你有很多属性可以以不同的方式组合,听起来装饰解决方案更像是你的小巷。

答案 4 :(得分:1)

我认为你走的是正确的道路:构图。您可以使用所需的其他类来编写您的类。但您还需要相应地委派责任。在您的示例中,Seed类应该负责检查其内部状态,而Apple只是委托给它。

至于可选功能问题,也许您可​​以使用null objects而不是空引用。这样,您无需每次都检查null,并且代码更加一致。

答案 5 :(得分:1)

我一直在思考这个问题,我想出了一个替代解决方案。这可能有点非正统和反面向对象,但如果你没有胆小的阅读......

以Apple示例为基础:Apple类可以包含许多属性,这些属性可以归类为相关组。举个例子,我推出了一个Apple类,其中包含一些与苹果种子相关的属性以及与苹果皮肤相关的其他属性。

  1. 苹果
    一个。种子
       A1。 GetSeedCount
       a2。 ...
     湾皮肤
        B1。 GetSkinColor
        B2。 ......
  2. 我正在使用字典对象存储所有苹果属性。

    我编写了扩展方法来定义属性的访问器,使用不同的类来保持它们的分离和组织。

    通过使用属性的字典,您可以随时迭代所有存储的属性(如果您必须检查所有属性,因为它听起来像您在更新方法中所需的那样)。遗憾的是,您丢失了强大的数据类型(至少在我的示例中,因为我使用的是Dictionary&lt; string,string&gt;。您可以为所需的每种类型使用单独的字典,但这需要更多的管道代码来路由属性访问正确的字典。

    使用扩展方法定义属性的访问器允许您为每个逻辑属性类别分隔代码。这可以将事物组织成单独的相关逻辑块。

    以下是我提出的一个示例,用于测试这是如何工作的,给出了标准警告,如果您要继续沿着此路径行进,则可以按顺序进行对齐(验证,错误处理等)。

    Apple.cs

    namespace ConsoleApplication1
    {
        using System.Collections.Generic;
        using System.Text;
    
        public class Apple
        {
            // Define the set of valid properties for all apple objects.
            private static HashSet<string> AllowedProperties = new HashSet<string>(
                new string [] {
                    "Color",
                    "SeedCount"
                });
    
            // The main store for all properties
            private Dictionary<string, string> Properties = new Dictionary<string, string>();
    
            // Indexer for accessing properties
            // Access via the indexer should be restricted to the extension methods!
            // Unfortunately can't enforce this by making it private because then extension methods wouldn't be able to use it as they are now.
            public string this[string prop]
            {
                get
                {
                    if (!AllowedProperties.Contains(prop))
                    {
                        // throw exception
                    }
    
                    if (Properties.ContainsKey(prop))
                    {
                        return this.Properties[prop];
                    }
                    else
                    {
                        // TODO throw 'property unitialized' exeception || lookup & return default value for this property || etc.
    
                        // this return is here just to make the sample runable
                        return "0"; 
                    }
                }
    
                set
                {
                    if (!AllowedProperties.Contains(prop))
                    {
                        // TODO throw 'invalid property' exception
    
                        // these assignments are here just to make the sample runable
                        prop = "INVALID";
                        value = "0";
                    }
    
                    this.Properties[prop] = value.ToString();
                }
            }
    
            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
    
                foreach (var kv in this.Properties)
                {
                    sb.AppendFormat("{0}={1}\n", kv.Key, kv.Value);
                }
    
                return sb.ToString();
            }
        }
    }
    

    <强> AppleExtensions.cs

    namespace AppleExtensionMethods
    {
        using System;
        using ConsoleApplication1;
    
       // Accessors for Seed Properties
        public static class Seed
        {
            public static float GetSeedCount(this Apple apple)
            {
                return Convert.ToSingle(apple["SeedCount"]);
            }
    
            public static void SetSeedCount(this Apple apple, string count)
            {
                apple["SeedCount"] = count;
            }
        }
    
       // Accessors for Skin Properties
        public static class Skin
        {
            public static string GetSkinColor(this Apple apple)
            {
                return apple["Color"];
            }
    
            public static void SetSkinColor(this Apple apple, string color)
            {
                apple["Color"] = ValidSkinColorOrDefault(apple, color);
            }
    
            private static string ValidSkinColorOrDefault(this Apple apple, string color)
            {
                switch (color.ToLower())
                {
                    case "red":
                        return color;
    
                    case "green":
                        return color;
    
                    default:
                        return "rotten brown";
                }
            }
        }
    }
    

    这是一个试驾:

    <强> Program.cs的

    namespace ConsoleApplication1
    {
        using System;
        using AppleExtensionMethods;
    
        class Program
        {
            static void Main(string[] args)
            {
                Apple apple = new Apple();
    
                apple.SetSkinColor("Red");
                apple.SetSeedCount("8");
    
                Console.WriteLine("My apple is {0} and has {1} seed(s)\r\n", apple.GetSkinColor(), apple.GetSeedCount());
    
                apple.SetSkinColor("green");
                apple.SetSeedCount("4");
    
                Console.WriteLine("Now my apple is {0} and has {1} seed(s)\r\n", apple.GetSkinColor(), apple.GetSeedCount());
    
                apple.SetSkinColor("blue");
                apple.SetSeedCount("0");
    
                Console.WriteLine("Now my apple is {0} and has {1} seed(s)\r\n", apple.GetSkinColor(), apple.GetSeedCount());
    
                apple.SetSkinColor("yellow");
                apple.SetSeedCount("15");
    
                Console.WriteLine(apple.ToString());
    
                // Unfortunatly there is nothing stopping users of the class from doing something like that shown below.
                // This would be bad because it bypasses any behavior that you have defined in the get/set functions defined
                // as extension methods.
                // One thing in your favor here is it is inconvenient for user of the class to find the valid property names as
                // they'd have to go look at the apple class. It's much easier (from a lazy programmer standpoint) to use the
                // extension methods as they show up in intellisense :) However, relying on lazy programming does not a contract make.
                // There would have to be an agreed upon contract at the user of the class level that states, 
                //  "I will never use the indexer and always use the extension methods!"
                apple["Color"] = "don't panic";
                apple["SeedCount"] = "on second thought...";
    
                Console.WriteLine(apple.ToString());
            }
        }
    }
    

    从7月11日(日期而非商店)处理您的评论:)
    在您提供的示例代码中,有一条注释声明:

      

    “正如你所看到的,我无法打电话   关于“怪物”的BasicBroodmother方法

    你意识到你可以在这一点上做这样的事情:

    BasicBroodmother bm = monster as BasicBroodmother;
    if (bm != null)
    {
        bm.Eat();
    }
    

    你的代码没有多少肉,(我知道这仅仅是一个例子),但当我看到它时,我感觉你应该能够改进设计。我的直接想法是为育母母提供一个抽象类,它将包含所有兄弟会共同的任何属性/动作的默认实现。然后专门的育雏师,如魔法育母,将包含特定于魔法育母的任何特殊属性/动作,但也从抽象类继承,并在必要时覆盖nessecary基础属性/动作。

    我会看一下动作设计的策略模式,以便根据怪物的类型可以交换动作(例如吃,生成,攻击等行为)。

    [编辑7/13]
    现在没时间详细介绍(需要睡觉),但我把一些sample code放在一起显示了不同的方法。

    代码包括:

    • Broodfather.cs - 抽象类,充满了不同Broodfathers“类型”的所有共同点。“
    • BasicBroodFather.cs - 继承自Broodfather的具体类。
    • BroodfatherDecorator.cs - 所有Broodfather装饰者都要继承的抽象类。
    • MagicalBroodfather.cs - 这个班用“神奇”来装饰/包裹一个兄弟
    • BloodthirstyBroodfather.cs - 这个班用“嗜血”来装饰/包裹一个兄弟。
    • program.cs - 演示了两个例子:第一个开始于一个基本的育雏,它被魔法包裹,然后是嗜血。第二个是从一个基本的育雏开始,然后将其包裹在嗜血的另一个顺序中,然后是魔术。

答案 6 :(得分:0)

也许你的方法不是他们应该的?

如果将Seed类与Apple类分开,为什么不将使用种子信息的方法移动到Seed类呢?

如果这些方法需要有关其他Apple属性的信息,您可以将其作为参数传递。

通过这样做,我猜你可以取消初始化检查......

这是一本关于如何解决这类问题的好书:

Refactoring

答案 7 :(得分:-4)

  

我的主要目标:我想要短代码。

选项:

  1. 将所有函数重写为static,并为每个函数创建一个类。
  2. 在Perl中重写您的代码库。
  3. 删除所有评论。