参考以下链接:
http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html?page=2
代码重用的组合方法提供了比继承更强的封装,因为对后端类的更改不需要破坏任何仅依赖于前端类的代码.例如, 更改前一个示例中Fruit的peel()方法的返回类型不会强制更改Apple的界面,因此无需破坏Example2的代码.
当然如果你改变返回类型peel()(见下面的代码),这意味着getPeelCount()将无法再返回int?你不是必须改变界面,否则会得到编译器错误?
class Fruit {
// Return int number of pieces of peel that
// resulted from the peeling activity.
public int peel() {
System.out.println("Peeling is appealing.");
return 1;
}
}
class Apple {
private Fruit fruit = new Fruit();
public int peel() {
return fruit.peel();
}
}
class Example2 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();
}
}
Run Code Online (Sandbox Code Playgroud)
使用合成,更改类Fruit不需要您更改Apple,例如,让我们改变peel以返回a double:
class Fruit {
// Return String number of pieces of peel that
// resulted from the peeling activity.
public double peel() {
System.out.println("Peeling is appealing.");
return 1.0;
}
}
Run Code Online (Sandbox Code Playgroud)
现在,类Apple会警告精度会丢失,但是你的Example2类会很好,因为组合更"松散",组合元素的更改不会破坏组合类API.在我们的案例中,只需改变Apple如下:
class Apple {
private Fruit fruit = new Fruit();
public int peel() {
return (int) fruit.peel();
}
}
Run Code Online (Sandbox Code Playgroud)
如果从()Apple 继承,您不仅会收到有关不兼容的返回类型方法的错误,而且还会收到编译错误.Fruitclass Apple extends FruitExample2
**编辑**
让我们从头开始,给出一个"真实世界"的组合与继承的例子.请注意,合成不仅限于此示例,还有更多用例可以使用该模式.
应用程序将形状绘制到画布中.应用程序不需要知道它必须绘制哪些形状,实现位于继承抽象类或接口的具体类中.但是,应用程序知道它可以创建多少不同的混凝土形状,因此添加或删除混凝土形状需要在应用程序中进行一些重构.
interface Shape {
public void draw(Graphics g);
}
class Box implement Shape {
...
public void draw(Graphics g) { ... }
}
class Ellipse implements Shape {
...
public void draw(Graphics g) { ... }
}
class ShapeCanvas extends JPanel {
private List<Shape> shapes;
...
protected void paintComponent(Graphics g) {
for (Shape s : shapes) { s.draw(g); }
}
}
Run Code Online (Sandbox Code Playgroud)
应用程序正在使用本机库来处理某些数据.实际的库实现可能已知,也可能未知,并且将来可能会或可能不会发生变化.这样就创建了一个公共接口,并在运行时确定了实际的实现.例如 :
interface DataProcessorAdapter {
...
public Result process(Data data);
}
class DataProcessor {
private DataProcessorAdapter adapter;
public DataProcessor() {
try {
adapter = DataProcessorManager.createAdapter();
} catch (Exception e) {
throw new RuntimeException("Could not load processor adapter");
}
}
public Object process(Object data) {
return adapter.process(data);
}
}
static class DataProcessorManager {
static public DataProcessorAdapter createAdapter() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
String adapterClassName = /* load class name from resource bundle */;
Class<?> adapterClass = Class.forName(adapterClassName);
DataProcessorAdapter adapter = (DataProcessorAdapter) adapterClass.newInstance();
//...
return adapter;
}
}
Run Code Online (Sandbox Code Playgroud)
因此,正如您所看到的,组合可能提供一些优于继承的优势,因为它允许代码具有更大的灵活性.它允许应用程序具有可靠的API,而底层实现在其生命周期中可能仍会发生变化.如果使用得当,组合物可以显着降低维护成本.
例如,在使用JUnit for Exemple 2实现测试用例时,您可能希望使用虚拟处理器并设置DataProcessorManager返回此类适配器,同时在生产中使用"真实"适配器(可能依赖于OS)而不更改应用程序源代码.使用继承,你很可能会破解一些东西,或者可能会编写更多的初始化测试代码.
正如你所看到的,compisition和inheritance在很多方面都有所不同,并不比另一方面更受欢迎 ; 每个都取决于手头的问题.你甚至可以混合继承和组合,例如:
static interface IShape {
public void draw(Graphics g);
}
static class Shape implements IShape {
private IShape shape;
public Shape(Class<? extends IShape> shape) throws InstantiationException, IllegalAccessException {
this.shape = (IShape) shape.newInstance();
}
public void draw(Graphics g) {
System.out.print("Drawing shape : ");
shape.draw(g);
}
}
static class Box implements IShape {
@Override
public void draw(Graphics g) {
System.out.println("Box");
}
}
static class Ellipse implements IShape {
@Override
public void draw(Graphics g) {
System.out.println("Ellipse");
}
}
static public void main(String...args) throws InstantiationException, IllegalAccessException {
IShape box = new Shape(Box.class);
IShape ellipse = new Shape(Ellipse.class);
box.draw(null);
ellipse.draw(null);
}
Run Code Online (Sandbox Code Playgroud)
当然,这最后一个例子是不干净(意味着,避免它),但它示出了组合物如何可以被使用.
底线是两个示例,DataProcessor并且Shape是"可靠"类,并且它们的API不应该更改.但是,适配器类可能会更改,如果它们这样做,这些更改应该只影响它们的组合容器,从而将维护限制为仅限于这些类而不是整个应用程序,而不是示例1,其中任何更改都需要在整个应用程序中进行更多更改.这一切都取决于您的应用程序需要多么灵活.