在我的继承树的不同类之间共享代码

时间:2016-03-13 23:13:13

标签: c#

假设我有以下继承树:

        ______ Object______
       /                   \
     Food              Material
   /     \            /        \
Egg       Carrot   Box          Axe

在C#中表达如此

class Object { ... }

class Food : Object { ... }
class OfficeMaterial : Object{ ... }    

class Box : OfficeMaterial { ... }
class Spoon : OfficeMaterial { ... }
class Egg : Food { ... }
class Carrot : Food { ... }

现在,我想在 Box Egg 之间共享功能,例如:

bool opened = true;

public void Open() { open = true; }
public void Close() { open = false; }
public bool IsOpen() { return opened; }

这是一个简短的例子,但它可能会更长。

这样做的正确方法是什么?如果我使用继承,其他类如 Carrot Ax 将获得它,这是我不希望的。

更新 很多人提到Composition。有人能告诉我在我提出的例子中它会如何起作用吗?

4 个答案:

答案 0 :(得分:2)

您有几个选择:

  1. 使用接口
  2. 前:

    public interface IOpenable
    {
        void Open();
        void Close();
        bool IsOpen();
    }
    

    然后任何需要openable / closable行为的类都可以相应地实现接口。

    1. 支持composition over inheritance并将您的对象从实现所需行为的其他对象中组合出来。

答案 1 :(得分:0)

为什么不使用界面? IOpenable

interface IOpenable {
    void Open();
    void Close();
    bool IsOpen();
}

答案 2 :(得分:0)

当我考虑EggBox之间不是CarrotAxe共享属性的相似之处时,我首先想到的是它们包含的内容。< / p>

  1. EggBoxContents可以曝光,隐藏,添加,删除等。
  2. 请注意此处的语言 - EggBox Contents一词(或有)表示应将object代表另一个对象的Contents用作组合元素。

    Egg 是{/ 1}}的类型。 Food EggContentsYolk

    EggWhite 类型的BoxOfficeMaterial BoxContents

    我可以这样写:

    OfficeSupplies

    这将进一步允许我:

    public class Food : Object 
    {
        public void Eat() { }
    }
    
    public class OfficeMaterial : Object { }
    
    public class Contents : Object 
    {
        bool _visible = false;
    
        List<Object> _things = new List<Object>();
    
        public int Count { get { return _things.Count; } }
    
        public bool IsOpen { get { return _visible; } }
    
        public void Expose()
        {
            _visible = true;
        }
    
        public void Hide()
        {
            _visible = false;
        }
    
        public void Add(object thing)
        {
            _things.Add(thing);
        }
    
        public bool Remove(object thing)
        {
            return _things.Remove(thing);
        }
    }
    
    public class Box : OfficeMaterial 
    {
        public Contents BoxContents = new Contents();
    }
    
    public class Egg : Food 
    {
        public Contents EggContents = new Contents();
    }
    

    然后我想

    void PlaceEggsInBox(int numEggs, Box box)
    {
        box.BoxContents.Expose();
    
        if (box.BoxContents.AreVisible)
        {
            int eggsInBox = box.BoxContents.Things.Count(thing => thing is Egg);
    
            for (int i = 0; i < numEggs - eggsInBox; i++)
            {
                box.BoxContents.Add(new Egg());
            }
        }
    }
    

    void EatAllEggsInBox(Box box) { box.BoxContents.Expose(); foreach (Egg egg in box.BoxContents.Things.Where(thing => thing is Egg)) { box.BoxContents.Remove(egg); egg.EggContents.Expose(); if (egg.EggContents.AreVisible) egg.Eat(); } } 方法是Eat()类的方法。 Food方法是Expose()类的方法,不是Contents类或Food类,也不是OfficeMaterials也不是Egg类。

答案 3 :(得分:0)

实际上是多重继承,C#除了通过接口以外不支持。

一个选择是创建一个没有 接口的Open方法(否则,您将不得不实现它,因此这仅用于输入),然后在一个静态类中实现一个静态扩展方法您的界面。

public Interface IOpenable { }

public static class IOpenableExtensions {
  public static Open(this IOpenable obj){
      // work on any concrete implementation of IOpenable
  }
}

用法:

var box = new Box; // Box implements IOpenable
var egg = new Egg; // Egg implements IOpenable
box.Open();
egg.Open();

当实际继承反映核心结构或组织,而接口提供某些对象共享的公共“行为”时,此模式可能会很好地工作。一个类可以实现一个或多个这样的接口。

C#8默认接口实现可能会使这种模式更加普遍。

实际上,在使用处理基类的函数时,您通常最终需要将对象强制转换为接口。

public IEnumerable<Food> GetFoods(){ ... }

public void HandleFood() {
    for(var food in GetFoods()) {
        if(food is IOpenable openableFood){ 
             openableFood.Open();
        }
    }
}

最后,请注意,这不是真正的多重继承,因为您无法通过接口层次结构继承/覆盖扩展方法本身。如果您同时拥有两个Open()扩展方法的IOpenableFood继承IOpenable,则不能在派生方法中调用base(),而必须选择要显式使用的扩展方法。最好完全避免这种情况。

var egg = new Egg(); // Egg inherits both IOpenable and IOpenableFood
egg.Open(); // Error: Ambiguous method
IOpenableFoodExtensions.Open(egg); // OK, call that method
IOpenableExtensions.Open(egg); // OK, call other method