类
类,接口和实现类,多个类实现一个接口的 UML 图。
继承
继承是指在根据已有类创建新类的能力。继承最主要的好处是代码复用。如果你想要创建的类与已有的类差异不大,那也没必要重复编写相同的代码。你只需扩展已有的类并将额外功能放入生成的子类(它会继承父类的成员变量和方法)中即可。
多态
绝大部分 动物 Animals 可以发出声音。我们需要所有子类都重写基类的 makeSound发出声音 方法, 让每个子类都发出正确的声音, 因此我们可以马上将其声明为抽象。这让我们得以忽略父类中该方法的所有默认实现,从而强制要求所有子类自行提供该方法的实现。
关系
关系1 - 依赖
依赖是类之间最基础的、也是最微弱的关系类型。如果修改一个类的定义可能会造成另一个类的变化,那么这两个类之间就存在依赖关系。当你在代码中使用具体类的名称时,通常意味着存在依赖关系。
依赖是最弱、最宽泛的关系,表示一个类在某个方法中使用了另一个类(例如,作为参数、局部变量、或调用其静态方法)。
1 | 教授 ----> 课程 |
关系2 - 关联
关联是一个对象使用另一对象或与另一对象进行交互的关系, 关联比依赖关系更强,
1 | 教授 ————> 课程 |
关联体现为两个类或类与接口之间语义级别上的一种强依赖关系,这种关系不是偶然性与临时性的,而是长期的,关联双方通常是平等的,
关联则意味着一个类持有另一个类的引用(作为成员变量),这种持有本身就构成了一种使用关系,因此必然存在依赖。
换句话说,关联是一种特殊的、更强的依赖。当你在类A中声明一个类B的成员变量时,类A不仅依赖于类B(因为编译时就需要知道B),而且与B建立了长期的关联。
因此,存在关联关系,就一定存在依赖关系;但存在依赖关系,不一定存在关联关系。
关系3 - 聚合
聚合是一种特殊类型的关联,用于表示多个对象之间的“一对多”、“多对多”或“整体对部分”的关系。普通关联仅用于描述两个对象之间的关系。通常在聚合关系中,一个对象“拥有”一组其他对象,并扮演着容器或集合的角色。组件可以独立于容器存在, 也可以同时连接多个容器。
特点:部分(Part)可以独立于整体(Whole)存在。
生命周期:整体和部分的生命周期不绑定。即使整体被销毁,部分仍然可以存在。
UML表示:用空心菱形 + 实线箭头表示,菱形指向整体。
示例:班级-学生、汽车-轮胎
关系4 - 组合
组合是一种特殊类型的聚合,其中一个对象由一个或多个其他对象实例构成。组合与其他关系的区别在于组件仅能作为容器的一部分存在。
特点:部分不能独立于整体存在,整体对部分有完全的控制权。
生命周期:部分的生命周期完全依赖于整体。整体创建时创建部分,整体销毁时销毁部分
。
UML表示:用实心菱形 + 实线箭头表示,菱形指向整体。
示例:公司-部门、订单-订单项
组合优于继承继承可能是类之间最明显、最简便的代码复用方式。如果你有两个代码相同的类, 就可以为它们创建一个通用的基类,然后将相似的代码移动到其中。轻而易举!不过,继承这件事通常只有在程序中已包含大量类,且修改任何东西都非常困难时才会引起关注。下面就是此类问题的清单。子类不能减少超类的接口。你必须实现父类中所有的抽象方•法,即使它们没什么用。
继承打破了超类的封装,因为子类拥有访问父类内部详细内•容的权限。此外还可能会有相反的情况出现,那就是程序员为了进一步扩展的方便而让超类知晓子类的内部详细内容。子类与超类紧密耦合。超类中的任何修改都可能会破坏子类•的功能。
组合是代替继承的一种方法。继承代表类之间的“是”关系(汽车是交通工具),而组合则代表“有”关系(汽车有一个引擎)。
比如这个汽车的例子
正如你所看到的,每个额外参数都将使子类数量倍增。子类中将有大量的重复代码,因为子类不能同时继承两个类
组合的还有一个好处是你可以在运行时对行为进行替换。例如,你可以通过重新为汽车对象分配一个不同的引擎对象来替换已连接至汽车的引擎。
关系强弱
多个来源都确认了关系的强弱顺序为:组合 > 聚合 > 关联 > 依赖
面向接口
面向接口进行开发, 而不是面向实现; 依赖于抽象类型,而不是具体类。
确定一个对象对另一对象的确切需求:它需执行哪些方法(其实就是对原子能力的理解和抽象)?
- 在一个新的接口或抽象类中描述这些方法。
- 让被依赖的类实现该接口。
- 现在让有需求的类依赖于这个接口, 而不依赖于具体的类。
- 你仍可与原始类中的对象进行互动,但现在其连接将会灵活得多。
抽取是有成本的:抽取接口前后的对比。右侧的代码要比左侧更加灵活,但也更加复杂。
改造前
改造后