java 类实现替换继承
相当早以前,我发表了一篇文章,尽管尽管您应该更喜欢组合而不是继承,但您还是可以最好地设计继承类。 现在,我想举一些例子,说明如何获取使用继承的代码并将其更改为使用合成,这实际上通常会使您的代码更灵活。
该代码将使用Java,但是这些概念可以转移到任何面向对象的语言。 某些语言可能也具有使部分操作变得容易的构造(Kotlin的代表)。
但是,要记住一件事很重要:即使您将“完全”切换为合成,您仍然会有一些继承。 不过,这种继承将仅来自接口和类似于接口的类。 实际上,它几乎不算作继承,因为它所做的只是将自身限制为API,而不继承实现细节。
基本构成:代表模式
委托模式不是一个非常严格定义的模式,您甚至可以说它只是构成的同义词。 尽管Adapter Pattern当然可以视为合成对象,并且可以肯定地代表包装对象,但我不认为它是Delegate Pattern。 可以,但是可以,但是我更喜欢一个更严格的定义,在这个定义中,委托和包装器实现相同的接口。 这仍然适合大多数基于构图的“模式”,但忽略了不太明显的情况。
好吧,让我们从我们的代码将要使用的接口开始。 我们将使用Foo作为界面。
public interface Foo
{
void bar();
int baz(int i);
}
现在我们需要一个基本实现,在将关系转换为合成之前,我们将尝试继承该实现。 我们将其称为BasicFoo 。
public class BasicFoo implements Foo
{
public void bar()
{
System.out.println("method one called");
}
public int baz(int i)
{
return i * 2;
}
}
最后,我们需要一个继承自BasicFoo的类。 我们将其称为ComplexFoo 。
public class ComplexFoo extends BasicFoo
{
@Override
public int baz(int i)
{
return super.baz(i) * 8;
}
}
在大多数情况下, ComplexFoo只是继承的实施BasicFoo ,但它确实使传入参数上验证baz()
因此,将ComplexFoo更改为使用合成而不是继承是什么样子? 像这样!
public class ComplexFoo implements Foo
{
private Foo wrapped;
public ComplexFoo(Foo wrapped)
{
this.wrapped = wrapped;
}
public void bar()
{
wrapped.bar();
}
public int baz(int i)
{
return wrapped.baz(i) * 8;
}
}
而不是从BasicFoo继承, ComplexFoo仅从接口Foo继承。 为了访问基本功能(例如BasicFoo的功能),它在其构造函数中接受Foo对象,然后将所有方法委托给该对象,在委托调用之前或之后放入额外的功能,就像您正在通过super “委派”。
限制代表
如果您只希望ComplexFoo仅能够“扩展” BasicFoo而没有其他基本实现该怎么办? 首先,这是非常严格的,因此可能不应该这样。 但是,如果您有充分的理由走那条路,则有几种选择。
第一种选择是仅在构造函数中接受BasicFoo 。 如果BasicFoo是final (不能扩展;实际上,尤其是在您大多数情况下进行合成,则BasicFoo建议这样做),这是BasicFoo ,但如果不是,则最终可能会继承自BasicFoo而不是实际的BasicFoo对象。
第二种选择是对其进行硬编码以在构造函数中创建BasicFoo 。 只需看到“硬编码”一词,您就知道这不是推荐的过程。
我的最后一个选择是使构造函数private或打包私有,仍然接受任何Foo对象,并使用静态工厂方法创建BasicFoo实例并将其传递给构造函数。 这样,如果BasicFoo对于测试有问题,则可以在测试中传递Foo的测试双倍。
更换模板图案
替换模板模式的最简单方法是改为使用策略模式。 通常,我喜欢认为Strategy对象只有一个公共方法,但这主要是因为它使您可以更轻松地将其转换为lambda或方法引用。 实际上,策略模式允许策略对象实现许多方法来实现其策略接口。 您可能不会从这种模式的观点中受苦,但是为了以防万一,我想与您分享。
我想你想举个例子,是吗?
public abstract class Bar
{
public int foo(int i)
{
i = bar(i);
i = baz(i);
return qux(i);
}
abstract protected int bar(int i);
abstract protected int baz(int i);
abstract protected int qux(int i);
}
Bar使用模板模式,保留一些方法的protected以便子类填充它们以供Bar的public方法使用。 我希望我不必给您一个示例子类。 如果您对模板模式感到困惑,请查询一下。
让我们将此模板模式转换为策略模式:
public class Bar
{
private BarStrategy strategy;
public Bar(BarStrategy strategy)
{
this.strategy = strategy;
}
public int foo(int i)
{
i = strategy.bar(i);
i = strategy.baz(i);
return strategy.qux(i);
}
}
我们新的Bar类不再具有那些protected方法来处理。 相反,它接受BarStrategy并委托这些调用。
我最初有一个关于这个想法的现实例子,但是后来我意识到这就是策略模式。 在那之后,我认为任何示例都不会真正有帮助,因为“策略模式”的任何示例都是经过组合设计的“模板模式”的示例。
收益
那么,从继承转移到合成,我们将获得什么呢? 好吧,我们获得了更多的代码,因为我们经常必须用显式的委托调用来代替对“ super”类的隐式委托(某些语言将提供解决此问题的捷径,例如Kotlin的Degelates )。 是的,这是收获,但不是一件好事。 真正的帮助呢?
关于切换到合成而不是继承的最大帮助是缺少我所称的“组合地狱”。 这是Decorator Pattern旨在解决的最大问题,但是任何切换到合成的方法都可以帮助解决这个问题。 您想到了一个类扩展,该扩展提供了围绕基本功能的某些额外功能。 然后,您又提出了另一种功能不同的产品。 最终,您意识到自己想要同时拥有这两种课程。 如果使用继承进行此操作,则需要创建另一个以某种方式组合它们的类。 如果您使用了组合,那么您将拥有所需的一切,因为一个扩展可以接受,包装和委托给另一个扩展(包装基类)。
前面提到了使用组合物的另一个大好处:可测试性。 现在,您可以通过双击测试的替代品来替代“ super”类,从而仅测试该类的新功能。 这种可测试性是为了更好地遵循SRP。
不幸的是,使用组合通常会使创建对象变得更加困难,需要创建每个中间对象的实例,而不仅仅是创建特定的子类,但是由于大多数创建应该在某种Factory上下文中完成,所以这不是问题你必须处理很多事情。
避免一些明确
我在上面已经提到,组合比继承比继承更加明确,因为它要求您使用委托调用来填充所有方法。 我提到过某些语言提供的功能可让您捷径而行,但是如果您愿意扩展我上面使用的“类接口类”的含义,则可以使用不具有此类功能的语言来解决此问题。 由于我最喜欢的模式Decorator模式使用它,因此我愿意对其进行扩展。
我指的是创建一个抽象类,其整个目的是委托。 这是Decorator模式中的Decorator基类。 这是一个充实而又抽象的类,从技术上讲什么也不做,因为它将所有调用委托给包装的对象。 我对创建这样一个要继承的类没有任何疑虑。 由于它实际上什么也不做,因此可以将其视为“类接口”。 真正的好处是您可以继承它,并且如果您不对方法进行任何更改,则无需包含它。
这不仅导致代码缩短,而且比SRP更好地遵循了SRP:一个类用于显式委派,另一类仅用于添加新功能(在需要时添加显式委派)。 它还删除了代码重复。 绝对值得继承“耦合”。
奥托罗
这就是我能带给您的关于“在继承中构成”的美好世界的全部信息。 在某些时候,继承仍然是最好的选择,但我敦促您在做出决定之前认真考虑组成。
翻译自: https://www.javacodegeeks.com/2015/05/replacing-inheritance-with-composition.html
java 类实现替换继承
本文探讨了在Java中使用合成而非继承的概念,通过具体示例展示了如何将使用继承的代码重构为使用合成,以此提高代码灵活性和可测试性。

5387

被折叠的 条评论
为什么被折叠?



