英文:
Should we prefer Composition over Inheritance when we can only use default constructor
问题
我了解组合优于继承的优势,但在某些情况下,类的实例是由框架使用默认构造函数创建的,我们既不能定义带参数的构造函数,也不能使用setter方法设置对象的属性。为了让这种情况更清楚,请考虑以下示例:
public class Main {
public static void main(String... str){
TargetFramework.component(Child.class);
}
}
这里的TargetFramework
获取一个class
,并且它将在后台使用默认构造函数创建该类的实例。
假设我想要实现以下FrameworkInterface
:
public interface FrameworkInterface {
void setup();
void doAction(Record record);
void doAnotherAction(Record record, boolean isValid);
}
现在,我可以通过继承和组合两种方式来实现这个接口:
方法1:(混合使用组合和继承)
public abstract class Parent implements FrameworkInterface {
RecordValidator recordValidator;
@Override
public abstract void setup();
@Override
public void doAction(Record record){
boolean isValid = recordValidator.validate(record);
doAnotherAction(record, isValid);
}
@Override
public void doAnotherAction(Record record, boolean isValid){
}
}
在这个实现中,我决定使用组合,并在Parent
类中定义了一个RecordValidator
。
这里的问题是,在创建这个类的实例时,我不能在Parent
类中设置RecordValidator
,因为这个类的实例是由框架使用默认构造函数创建的。但是我可以在继承Parent
类的子类的setup
方法中创建这个实例:
public class Child extends Parent {
@Override
public void setup() {
recordValidator = new DefaultRecordValidator();
}
}
FrameworkInterface
的setup
方法会在通过默认构造函数创建实例后立即被调用,因此我们可以在其中初始化RecordValidator
属性。这种方法将组合和继承混合在一起,因为我同时使用了组合和继承。然而,这种方法也有其优点,因为我将记录验证的关注点从父类的关注点中分离出来。
方法2:(只使用继承)
在这种方法中,我通过以下方式实现了FrameworkInterface
:
public abstract class Parent1 implements FrameworkInterface {
@Override
public void setup() {
}
@Override
public void doAction(Record record) {
boolean isValid = validate(record);
doAnotherAction(record, isValid);
}
@Override
public void doAnotherAction(Record record, boolean isValid) {
}
protected abstract boolean validate(Record record);
}
这样,我没有使用组合来定义RecordValidator
,而是在Parent1
类中定义了抽象的validate
方法,以便子类可以使用它来实现验证行为。因此,Child
类可以这样实现:
public class Child extends Parent1 {
@Override
protected boolean validate(Record record) {
return false;
}
}
我的问题是:
对于这种情况,哪种方法更好,它们各自的优缺点是什么?
英文:
I know the advantages of Composition over Inheritance but in some situation instances of the class are being created by framework using default constructor and we can not define constructor with parameter nor we can set attribute of the object using setter methods. To make this situation clear consider following example:
public class Main {
public static void main(String... str){
TargetFramework.component(Child.class);
}
}
Here the TargetFramework
get a class
and it will create instance of that class behind the scene using default Constructor.
Imagine I want to implement FramewrokInterface
as below:
public interface FrameworkInterface {
void setup();
void doAction(Record record);
void doAnotherAction(Record record, boolean isValid);
}
Now I can implement this interface in two ways considering Inheritance and Composition:
Approach 1: (Mixing and Matching Composition and Inheritance)
public abstract class Parent implements FrameworkInterface {
RecordValidator recordValidator;
@Override
public abstract void setup();
@Override
public void doAction(Record record){
boolean isValid = recordValidator.validate(record);
doAnotherAction(record, isValid);
}
@Override
public void doAnotherAction(Record record, boolean isValid){
}
}
In this Implementation I decided to use composition and I've defined a RecordValidator
as bellow:
public interface RecordValidator {
boolean validate(Record record);
}
The problem here is that I can't set RecordValidator
in Parent
class when creating instance of this class because instances of this class are created by framework using default constructor but I can create this instance in setup method in child Class which extends parent class as below:
public class Child extends Parent {
@Override
public void setup() {
recordValidator = new DefaultRecordValidator();
}
}
The setup method of the FramworkInterface
will be called just after instance created by default Constructor so we can use it to initialize our RecordValidator
attribute; This is kind of Mixing and Matching Composition and Inheritance together to me because I'm using Composition with Inheritance together. However this approach has its own advantages because I've separated the Concern of validation of record from the Parent class Concerns.
Approach 2: (Just Inheritance)
In this approach I've implemented the FrameworkInterface in the following way:
public abstract class Parent1 implements FrameworkInterface {
@Override
public void setup() {
}
@Override
public void doAction(Record record) {
boolean isValid = validate(record);
doAnotherAction(record, isValid);
}
@Override
public void doAnotherAction(Record record, boolean isValid) {
}
protected abstract boolean validate(Record record);
}
This way instead of using composition and defining RecordValidator
I've defined abstract validate
method in my Parent1
class so that Child class can use it to implement validation behaviour, so the Child
class can be implemented as follow:
public class Child extends Parent1 {
@Override
protected boolean validate(Record record) {
return false;
}
}
My question is:
Which approach is better for this situation and what are the pros and cons of them?
答案1
得分: 1
在这种情况下,哪种方法更好,它们的优缺点是什么?
我认为它们两者都不太理想,到了一定程度,我会寻找其他的解决方案。
从示范代码来看,例如在这两种情况下都没有可能模拟Child1
的依赖关系。你可以通过实现仅用于测试的设置器或特殊构造函数来引入模拟能力。然而,我对这个设置的核心问题是你屈从于这个框架。
我建议探索其他可能性,例如,手动进行必要的依赖注入,然后“注册”一个完成的 bean 到框架中。这就是当 Uncle Bob 谈论与框架保持一定距离时的意思。
如果我们特别谈论 Java,并且这个框架不允许其他解决方案,例如事先创建 beans 并将其注册到框架中,我会联系框架的维护者,并要求实现 CDI 支持,因为这是一种处理依赖注入的标准方法。
看着你的示例,你采取了两种不同的方法,也就是重新定义了 Parent
的能力。就像在继承示例中对 Parent
所做的那样,你可以在 Parent
中定义 abstract boolean validate();
,将实现委托给 Child
。我甚至会更进一步,定义:
public interface class Parent extends FrameworkInterface, RecordValidator {
...
}
(Parent
中的所有方法都是抽象的,或者可以看作是默认值,字段可以被移除)。因此,实现这个接口的每个类都会根据其自身情况实现这些方法。
英文:
Which approach is better for this situation and what are the pros and cons of them?
I would argue that both of them are suboptimal to a degree where I would look for other solutions.
Looking at the sample code, there is, for example, no possibility to mock the dependencies of Child1
in both situations. You could introduce mock capabilities by implementing setters or special constructors that are only used for testing. The core problem I have with this setup, however, is that you bow to the framework.
I would recommend exploring other possibilities, e.g. do the necessary dependency injection manually, then "register" a finished bean with the framework. This is what Uncle Bob means when he talks about keeping the framework at arm's length.
If we start talking about Java in particular and the framework does not allow any other solution to, e.g., create beans beforehand and registering them with the framework, I would contact the framework maintainers and ask to implement CDI support since this is a standardized way to handle Depencency Injection.
Looking at your example, you take two different approaches, i.e. you redefine the capabilites of Parent
. Just as you did with Parent
in the inheritance example, you could define abstract boolean validate();
in Parent
, delegating the implementation to Child
. I would even go a step further and define
public interface class Parent extends FrameworkInterface, RecordValidator {
...
}
(all methods in Parent
are either abstract or can be seen as defaults, the field can be removed). Thus, each class implementing this interface implements the methods as it sees fit.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论