设计模式
面向对象程序设计简介
面向对象程序设计基础

抽象

抽象是一种反映真实世界对象或现象中特定内容的模型, 它能高精度地反映所有与特定内容相关的详细信息, 同时忽略其他内容。
例如, 飞行模拟器和航班预订程序中都可能会包含一个 飞机Airplane类。 但是前者需包含与实际飞行相关的详细信息, 而后者则只关心座位图和哪些座位可供预订。
封装
封装是指一个对象对其他对象隐藏其部分状态和行为, 而仅向程序其他部分暴露有限的接口的能力。

以Java语言来说明UML图
class Airport {
void accept(FlyingTransport vehicle){} //依赖关系
}
interface FlyingTransport {
void fly(...){}
}
Helicopter implements FlyingTransport {
void fly(...){}
}继承
继承是指在根据已有类创建新类的能力。 继承最主要的好处是代码复用。

class Cat extends Animal implements Forlegged, OxygenBreather {}多态
多态是指程序能够检测对象所属的实际类, 并在当前上下文不知道其真实类型的情况下调用其实现的能力。

对象之间的关系
除了之前我们已见到的继承和实现之外, 对象之间还有其他我们尚未提及的关系。
依赖

依赖是类之间最基础的、 也是最微弱的关系类型。 如果修改一个类的定义可能会造成另一个类的变化, 那么这两个类之间就存在依赖关系。
通常情况下, UML 图不会展示所有依赖——它们在真实代码中的数量太多了。 为了不让依赖关系破坏 UML 图, 你必须对其进行精心选择, 仅展示那些对于沟通你的想法来说重要的依赖关系。
关联

关联是一个对象使用另一对象或与另一对象进行交互的关系。 在 UML 图中, 关联关系用起始于一个对象并指向其所使用的对象的简单箭头来表示。 顺带一提, 双向关联也是完全正常的, 这种情况就用双向箭头来表示。 关联可视为一种特殊类型的依赖, 即一个对象总是拥有访问与其交互的对象的权限, 而简单的依赖关系并不会在对象间建立永久性的联系。
一般来说, 你可以使用关联关系来表示类似于类成员变量的东西。 这个关系将一直存在, 因此你总能通过 “订单” 来获取其 “顾客”。 但是它并非一定是成员变量。 如果你根据接口来创建类, 它也可以表示为一个可返回 “订单” 的 “顾客” 的方法。
######## 依赖和关联的区别
class Professor is
field Student student
// ...
method teach(Course c) is
// ...
this.student.remember(c.getKnowledge())让我们来看看 teach (教授知识) 方法, 它将接收一个来自 课程 (Course) 类的参数。 如果有人修改了 课程类的 getKnowledge (获取知识) 方法 (修改方法名或添加一些必须的参数等), 代码将会崩溃。 这就是依赖关系。
现在, 让我们来看看名为 student (学生) 的成员变量, 以及如何在 teach方法中使用该变量。 我们可以肯定 学生 (Student) 类是 教授类的依赖: 如果 remember (记住) 方法被修改, 教授的代码也将崩溃。 但由于 教授的所有方法总能访问 student成员变量, 所以 学生类就不仅是依赖, 而也是关联了。
聚合

聚合是一种特殊类型的关联, 用于表示多个对象之间的 “一对多”、 “多对多” 或 “整体对部分” 的关系。 通常在聚合关系中, 一个对象 “拥有” 一组其他对象, 并扮演着容器或集合的角色。 组件可以独立于容器存在, 也可以同时连接多个容器。 在 UML 图中, 聚合关系使用一端是空心菱形, 另一端指向组件的箭头来表示。
尽管我们在此讨论的是对象之间的关系, 但请记住 UML 图表示的是类之间的关系。 这意味着大学对象可能是由多个院系构成的, 即便图中的每个实体只用一个 “方框” 来表示。 你可以使用 UML 符号在关系两端标明数量, 但如果可从上下文明确数量的话, 则可以省略此类标注。
组合

组合是一种特殊类型的聚合, 其中一个对象由一个或多个其他对象实例构成。 组合与其他关系的区别在于组件仅能作为容器的一部分存在。 在 UML 图中, 组合与聚合关系的符号相同, 但箭头起始处的菱形是实心的。
注意, 许多人常常在实际想说聚合和组合时使用 “组合” 这个术语。 其中最恶名昭彰的例子是著名的 “组合优于继承” 原则。 这并不是因为人们不清楚它们之间的差别, 而是因为 “组合 (例如 ‘对象组合’)” 说起来更顺口。
总结
解答 “聚合与组合有何区别?” 或 “继承是不是一种依赖关系?” 等问题

- 依赖: 对类 B 进行修改会影响到类 A 。
- 关联: 对象 A 知道对象 B。 类 A 依赖于类 B。
- 聚合: 对象 A 知道对象 B 且由 B 构成。 类 A 依赖于类 B。
- 组合: 对象 A 知道对象 B、 由 B 构成而且管理着 B 的生命周期。 类 A 依赖于类 B。
- 实现: 类 A 定义的方法由接口 B 声明。 对象 A 可被视为对象 B。 类 A 依赖于类 B。
- 继承: 类 A 继承类 B 的接口和实现, 但是可以对其进行扩展。 对象 A 可被视为对象 B。 类 A 依赖于类 B。
设计模式简介
设计模式是软件设计中常见问题的典型解决方案。
模式的描述通常会包括以下部分:
- 意图部分简要地描述问题和解决方案。
- 动机部分进一步解释问题并说明模式会如何提供解决方案。
- 结构部分展示模式的各个部分和它们之间的关系。
- 在不同语言中的实现提供流行编程语言的代码, 让读者更好地理解模式背后的思想。
设计模式的分类
最通用的、 高层的模式是构架模式。 开发者可以在任何编程语言中使用这类模式。 与其他模式不同, 它们可用于整个应用程序的架构设计。
三种主要的模式类别:
- 创建型模式提供创建对象的机制, 增加已有代码的灵活性和可复用性。
- 结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
- 行为模式负责对象间的高效沟通和职责委派。

软件设计原则
优秀设计的特征
- 代码复用
- 扩展性
变化是程序员生命中唯一不变的事情。
设计原则
封装变化的内容
方法层面的封装
类层面的封装
面向接口进行开发,而不是面向实现
组合优于继承
组合是代替继承的一种方法。 继承代表类之间的 “是” 关系 (汽车是交通工具), 而组合则代表 “有” 关系 (汽车有一个引擎)。
必须一提的是, 这个原则也能应用于聚合 (一种更松弛的组合变体, 一个对象可引用另一个对象, 但并不管理其生命周期)。 例如: 一辆汽车上有司机, 但是司机也可能会使用另一辆汽车, 或者选择步行而不使用汽车。
SOLID 原则
SOLID 是让软件设计更易于理解、 更加灵活和更易于维护的五个原则的简称。
与生活中所有事情一样, 盲目遵守这些原则可能会弊大于利。 在程序架构中应用这些原则可能会使其变得过于复杂。 我对于是否真的有能够同时应用所有这五条原则的成功软件产品表示怀疑。 有原则是件好事, 但是也要时刻从实用的角度来考量, 不要把这里的每句话当作放之四海皆准的教条。
单一职责原则
Single Responsibility Principle
修改一个类的原因只能有一个。


开闭原则
Open/closed Principle
对于扩展, 类应该是 “开放” 的; 对于修改, 类则应是 “封闭” 的。


里氏替换原则
Liskov Substitution Principle
当你扩展一个类时, 记住你应该要能在不修改客户端代码的情况下将子类的对象作为父类对象进行传递。
替代原则包含一组对子类 (特别是其方法) 的形式要求。
- 子类方法的参数类型必须与其超类的参数类型相匹配或更加抽象。
- 子类方法的返回值类型必须与超类方法的返回值类型或是其子类别相匹配。
- 子类中的方法不应抛出基础方法预期之外的异常类型。
- 子类不应该加强其前置条件。
- 子类不能削弱其后置条件。
- 超类的不变量必须保留。
- 子类不能修改超类中私有成员变量的值。


接口隔离原则
Interface Segregation Principle
客户端不应被强迫依赖于其不使用的方法。
尽量缩小接口的范围, 使得客户端的类不必实现其不需要的行为。


依赖倒置原则
Dependency Inversion Principle
高层次的类不应该依赖于低层次的类。 两者都应该依赖于抽象接口。 抽象接口不应依赖于具体实现。 具体实现应该依赖于抽象接口。
低层次的类实现基础操作 (例如磁盘操作、 传输网络数据和连接数据库等)。
高层次类包含复杂业务逻辑以指导低层次类执行特定操作。
SPI(Service Provider Interface)
定义:SPI 是 Java 中的一种服务发现机制,允许框架或模块在运行时动态加载具体实现。例如 JDBC。


创建型模式
创建型模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性。
工厂方法
亦称:虚拟构造函数、Virtual Constructor、Factory Method
工厂方法是一种创建型设计模式,
其在父类中提供一个创建对象的
方法,允许子类决定实例化对象
的类型。
结构
- 产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。
- 具体产品 (Concrete Products) 是产品接口的不同实现。
- 创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。
尽管它的名字是创建者, 但它最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。
- 具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。
注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。
伪代码
// 创建者类声明的工厂方法必须返回一个产品类的对象。创建者的子类通常会提供
// 该方法的实现。
class Dialog is
// 创建者还可提供一些工厂方法的默认实现。
abstract method createButton():Button
// 请注意,创建者的主要职责并非是创建产品。其中通常会包含一些核心业务
// 逻辑,这些逻辑依赖于由工厂方法返回的产品对象。子类可通过重写工厂方
// 法并使其返回不同类型的产品来间接修改业务逻辑。
method render() is
// 调用工厂方法创建一个产品对象。
Button okButton = createButton()
// 现在使用产品。
okButton.onClick(closeDialog)
okButton.render()
// 具体创建者将重写工厂方法以改变其所返回的产品类型。
class WindowsDialog extends Dialog is
method createButton():Button is
return new WindowsButton()
class WebDialog extends Dialog is
method createButton():Button is
return new HTMLButton()
// 产品接口中将声明所有具体产品都必须实现的操作。
interface Button is
method render()
method onClick(f)
// 具体产品需提供产品接口的各种实现。
class WindowsButton implements Button is
method render(a, b) is
// 根据 Windows 样式渲染按钮。
method onClick(f) is
// 绑定本地操作系统点击事件。
class HTMLButton implements Button is
method render(a, b) is
// 返回一个按钮的 HTML 表述。
method onClick(f) is
// 绑定网络浏览器的点击事件。
class Application is
field dialog: Dialog
// 程序根据当前配置或环境设定选择创建者的类型。
method initialize() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
dialog = new WindowsDialog()
else if (config.OS == "Web") then
dialog = new WebDialog()
else
throw new Exception("错误!未知的操作系统。")
// 当前客户端代码会与具体创建者的实例进行交互,但是必须通过其基本接口
// 进行。只要客户端通过基本接口与创建者进行交互,你就可将任何创建者子
// 类传递给客户端。
method main() is
this.initialize()
dialog.render()适合应用场景
- 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
- 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
- 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。