本文为学习了孟宁老师的《高级软件工程》后对软件工程中的继承与组合关系的浅显理解。
参考资料:代码中的软件工程 https://gitee.com/mengning997/se
继承和组合是软件开发人员用来建立类和对象之间关系的两种编程技术。继承是面向对象编程(OOP)语言的核心概念之一。定义为从现有的类(例如超类或基类)派生新类(子类) ,然后将它们形成类的层次结构。继承可以简单地理解为“is-a”关系。在大多数基于类的面向对象语言中,通过继承创建的对象“子对象”获取“父对象”的所有属性和行为,除了:构造函数、析构函数、基类的重载运算符和友元函数。继承允许程序员创建基于现有类的类,指定新的实现同时保持相同的行为(实现接口),重用代码并通过公共类和接口独立扩展原始软件。样例如下:
//交通工具
class Vehicle {
String brand;
double speed;
void move() {
System.out.println("The vehicle is moving.");
}
}
//汽车
public class Car extends Vehicle {
String licensePlateNumber;
// 车型
String bodyStyle;
public static void main(String[] args) {
new Car().move();
}
}
组合也是面向对象编程的基本概念之一。组合和聚合是将对象或数据类型组合成更复杂的对象或数据类型的密切相关的方法。它描述了在实例变量中引用其他类的一个或多个对象的类。组合可以简单地理解为“has-a”关系。现实世界中经常可以找到这样的关系。例如,一辆汽车有一个引擎,一个人有一个心脏。组合关系是部分-整体关系,其中部分必须构成整个对象的一部分。例如,心脏是人身体的一部分。合成中的部分一次只能是一个对象的一部分。一颗属于一个人身体的心脏不能同时属于另一个人的身体。在组合关系中,对象负责部分的存在。大多数情况下,这意味着在创建对象时创建部件,并在销毁对象时销毁部件。但更广泛地说,这意味着对象以对象用户不需要参与的方式管理部件的生命周期。例如,当身体被创造出来时,心也被创造出来。当一个人的身体被摧毁时,他的心也被摧毁。因此,组合有时被称为“死亡关系”。样例如下:
class Bedroom { }
class LivingRoom { }
class House {
Bedroom bedroom;
LivingRoom livingRoom;
House(Bedroom bedroom, LivingRoom livingRoom) {
this.bedroom = bedroom;
this.livingRoom = livingRoom;
}
}
public class Main {
public static void main(String[] args) {
new House(new Bedroom(), new LivingRoom());
}
}
继承和组合的区别在于继承代码中的类和对象是紧耦合的,因为改变父类或超类可能会改变子类或子类,从而破坏代码。而在组合中,类和对象是松耦合的,这意味着我们可以更轻松地切换这些组件而不会破坏代码,因为松散耦合的代码提供了更大的灵活性。通常情况下,组合都可以替代继承。例如,Go和Rust都在语言层面放弃了继承,选择了组合。
选择组合而不是继承的具体原因如下:
- 继承削弱了类的封装。继承意味着对其他类加强封装性,而对父类和其子类削弱封装性。也就是说,继承把父类实现的细节暴露给子类,打破了封装的基本概念。同时,如果子类的行为依赖于父类的操作,那么子类就会变脆弱,健壮性不如之前。并且当父类的行为发生变化时,即使子类的代码没有任何改动,它的行为也可能被破坏。
- 组合的灵活性更好。组合不仅允许将一系列方法封装到自己的类中,只要正在使用的对象实现正确的行为接口,就可以在运行时更改代码方法的行为。不能在运行时更改从超类继承的实现,因为通过继承实现的类在编译时就已经确定了。
- 继承的代码测试起来比较复杂。在面向对象的设计中,封装使得测试整个代码更容易。由于继承削弱了封装,继承的代码测试起来会比较困难。与之相反,组合提供了比继承更好的类测试功能。如果一个类由另一个类组成,我们可以轻松地构造一个表示组合类的Mock对象进行代码的单元测试。
因此,通常情况下,组合优于继承。



