2012-04-24 52 views
2

在布置类层次结构时,我经常发现自己能够封装功能并共享代码之间存在差距。当然,部分问题是缺乏多重继承,但接口有所帮助。在界面上无法定义受保护的方法在我看来是更大的问题。正确设计用于代码共享和封装的Java类层次结构

标准解决方案似乎是通过受保护的抽象基类实现的公共接口。问题是,当我们有以下

public interface Foo { 
    public String getName(); 
} 

abstract protected BaseFoo implements Foo { 
    abstract protected int getId(); 

    private String name; 
    protected BaseFoo(String name) { 
     this.name = name; 
    } 

    @Override 
    public String getName() { 
     return this.name; 
    } 
} 

public class ConcreteFoo extends BaseFoo { 
    public ConcreteFoo (String name) { 
     super(name); 
    } 

    @Override 
    protected int getId() { 
     return 4; // chosen by fair dice roll. 
        // guaranteed to be random. 
    } 
} 

// in the foo package with the classes above 
public class FooCollection { 
    private static Map<Integer, Foo> foos = new HashMap(); 
    public static void add(Foo foo) { 
     synchronized(foos) { 
      foos.put(foo.getId(), foo); // can't call foo.getId() 
     } 
    } 
} 

// client code, not in the foo package 
FooCollection.add(new ConcreteFoo("hello world")); 

也就是说,我们回到我们很好地封装对象的一个​​来电者,但后来它获取该对象后面的任何方法需要能够依靠一些内部的功能。该内部功能不能成为接口的一部分(这会破坏封装),但要使其成为抽象基类的一部分,需要我们使用转换。

我们不能让Foo成为一个抽象类,因为其他接口需要扩展它以添加可选的正交功能,以便将其添加到比此处显示的更复杂的层次结构中。

这个问题的标准方法是什么?即使客户端不应该使用它,您是否将getId添加到Foo接口?你对FooCollection.add中的BaseFoo执行不安全转换吗?如果您在投射前进行检查,即使这些类型总是出于所有意图和目的,但在类型不匹配时您会做什么?

任何有关此类情况的最佳实践的信息都会非常有帮助。

编辑:如果不清楚,这个例子是故意过于简化。关键是有时候你会返回一个对象的“接口视图”。当这个“接口视图”被传递回到特定于包的类时,它传递给它的方法可能需要在其实现中使用内部功能。如何管理内部和公共功能之间的不匹配?

+1

如果你知道你需要'getId()',那么你有'ConcreteFoo'的集合,而不是'Foo'。 – jpm 2012-04-24 23:55:22

+0

考虑:如果Base *实现* Foo会怎么样? – 2012-04-25 00:00:42

+0

哎呀,那是一个错字。谢谢 – 2012-04-25 00:06:28

回答

6

好了,这里的几个点:

  1. 流行的观点相反,继承真的是不是共享代码。您在继承层次结构中创建的是一组共享某些常见抽象行为的事物组织;它只是有时会产生重用某些代码的效果。

  2. 在过去的几年中,时尚已经发生了很大的变化,因此深层复杂的继承层次不再被认为是一种好的形式。一般在Java中。你应该实现接口

  3. 使用的接口来表达“混合式”合同
  4. 使用继承只有在类描述的东西,有自然的遗产之前

    • 使用聚集。
  5. 如果您确实需要多重继承的效果,请为您的接口构建实现类并对它们进行聚合。

  6. 特别是,通过使用接口和实现类定义您的类,您可以使构建测试更加轻松;如果你的界面是分开的,为这个界面构建一个模拟器几乎是微不足道的。

+2

其实四点或七点。没有人期望西班牙的宗教裁判所。 – 2012-04-25 00:01:06

2

我不知道“最佳”做法,但这里有几个想法。

接口应该将“要做什么”与“如何做什么”分开。我不认为getter和setter属于接口。我试图给他们更有意义的签名。

在你的情况,我认为没有错有两个接口:

public interface Nameable { 
    String getName(); 
} 

public interface Identifiable { 
    int getId(); 
} 

将二者分开;迫使客户只实施他们需要的。您决定将id作为抽象类的一部分是任意的。将其分开并明确说明可能会有所帮助。

铸造失去了多态性的所有好处。我认为它不应该轻易放弃。如果您必须将getId()移至界面,请执行此操作。如果你可以通过不同的选择来避免它,那就这样做。

“最佳”取决于您的环境。在所有情况下,您的简单示例可能都不是真实的。

+0

感谢您的回复。我的例子非常简单,但也许我会过度简化。关键是有时候你会返回一个对象的“接口视图”。当这个“接口视图”被传递回到特定于包的类时,它传递给它的方法可能需要在其实现中使用内部功能。如何管理内部和公共功能之间的不匹配? – 2012-04-25 00:04:09

+0

如果您打算返回接口引用类型,则必须指向具有该引用的用户需要的所有功能的对象,否则您必须投射该对象。没有神奇的配方。 – duffymo 2012-04-25 00:15:23