第一章 设计模式七大原则
1.设计模式目的
设计模式可以使程序高内聚,低耦合,具有可读性(编程规范性,便于其它程序员理解),可维护性(增加新功能很方便),可重用性(相同的代码,不用多次编写),可靠性(增加新功能,对原来的功能没有影响)。
设计模式在软件中出现的位置:面向对象语言(oo)=>功能模块(设计模式+算法(数据结构)) => 框架(使用多种设计模式) => 架构(服务器集群)。
2.设计模式七大原则
设计模式原则是程序编写时应当遵守的规范,也算是设计模式的基础。(即设计模式的依据)。
- 单一职责原则
- 接口隔离原则
- 依赖倒转(倒置)原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
2.1 单一职责原则
单一职责指的是:一个类应该只负责一项职责。
注意事项和细节:
1)降低类的复杂度,一个类只负责一项职责。
2)提高类的可读性和可维护性。
3)降低变更引起的风险。
4)通常情况下,应该遵守单一职责原则。但如过类中方法数量足够少,可以在方法级别保持单一职责原则。
2.2 接口隔离原则
接口隔离指的是:一个类不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。就是说,一个类通过接口去依赖该接口的实现类的时候,如果说并不需要使用该接口的所有方法时,应该将接口进行分解成多个接口,减少没必要的依赖,不然导致接口的实现类需要实现不需要使用的方法。
2.3 依赖倒转(倒置)原则
依赖倒转指的是:面向接口编程。就是该类依赖另一个类时,通过接口或抽象类的方式进行依赖。即变量的声明类型尽量是接口或者抽象类。这样变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
依赖倒转的设计理念:相对于细节的多变性,抽象的东西更加稳定。以抽象为基础搭建的架构比以细节为基础搭建的架构更稳定。在java中,抽象指的是接口或抽象类,细节则是具体的实现类。
依赖关系传递的三种方式
- 方法中,接口/抽象类传递
- 构造方法传递
- setter方法传递
2.4 里氏替换原则(不要重载和重写父类的方法)
里氏替换指的是:所有引用基类的地方必须能透明的使用子类对象。即子类不要去覆写父类的方法,避免覆写手误覆写了父类的方法A,在去使用父类的方法A,就会引发问题。如果需要单独新增方法时,为了还能使用父类原来的方法,则需要将原来的父类抽象成一个接口C,再额外写一个类A去实现该接口C。那么自己也实现该接口C,如果需要使用该接口C的方法时,则可以通过依赖,聚会,组合等方式去引用实现了该接口C的类A。类的关系如下图:
2.5 开闭原则
开闭原则是指:一个软件实体如类,模块和函数应该对扩展开发(对提供方),对修改关闭(对使用方)。即提供方增加或修改代码时,使用方无需改动。用抽象构架框架,用实现扩展细节。它是编程中最基础,最重要的设计原则。其它设计原则都是遵循了开闭原则。
2.6 迪米特法则
1)一个对象应该对其它对象保持最少的了解。
2)类与类关系越密切,耦合度越大。
3)迪米特法则又叫最少知道原则。即一个类对自己依赖的类知道的越少越好。就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外除了提供public 方法,不对外泄露任何信息。
4)迪米特法则还有个简单的定义,只与直接的朋友通信。
5)直接朋友:只要两个对象间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。我们称出现在成员变量,方法参数,方法返回值的类为直接朋友。而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
6)比如说我有一个打印员工信息的方法,则员工类直接搞一个打印方法,而不是提供获取所有员工的方法,让使用类去打印。减少耦合。
2. 7合成复用原则
原则是尽量使用组合/聚合来代替继承。
设计原则核心思想
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 针对接口编程,而不是针对实现编程。
- 为了交互对象之间的松耦合设计而努力。
UML
UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思考的结果。工具:Rational Rose,也可以使用一些插件来建模。
聚合(整体与部分可以分开)。组合(整体与部分不可以分开)。
第二章 设计模式
1.标准设计模式分类
设计模式定位:设计模式不是代码,它是一类问题的通用解决方案。
- 创建型模式(创建对象角度5):单例模式、[简单]工厂模式、原型模式、抽象工厂模式、建造者模式。[工厂方法模式]
- 结构型模式(系统结构角度7):代理模式、适配器模式、桥接模式、装饰模式、外观模式、享元模式、组合模式。
- 行为型模式(方法的设计角度11):模板方法模式、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式、解释器模式。
2.单例设计模式
单例模式有八种:
1.饿汉式(静态常量)
2饿汉式(静态代码块)
3.懒汉式(线程不安全)
4.懒汉式(线程安全,同步方法)
5.懒汉式(线程安全,同步代码块)
6.双重检查
7.静态内部类
8.枚举
代码
/**
* 1.构造方法私有化(防止new)
* 2.类的内部创建对象
* 3.对外提供一个静态方法来获取单例对象
*/
//1.饿汉式(静态变量) 线程安全,没有实现懒加载,可能造成浪费内存空间
class Singleton1{
private static final Singleton1 instance = new Singleton1();
private Singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}
//1.饿汉式(静态代码块)
class Singleton2{
private static Singleton2 instance;
static{
instance = new Singleton2();
}
private Singleton2(){}
public static Singleton2 getInstance(){
return instance;
}
}
//2.懒汉式(线程不安全)
class Singleton3{
private static Singleton3 instance;
private Singleton3(){}
public static Singleton3 getInstance(){
if(instance==null){
instance = new Singleton3();
}
return instance;
}
}
//2.懒汉式(线程安全,同步方法,效率低)
class Singleton4{
private static Singleton4 instance;
private Singleton4(){}
public static synchronized Singleton4 getInstance(){
if(instance==null){
instance = new Singleton4();
}
return instance;
}
}
//3.双重检查(线程安全,效率较高,推荐使用)
class Singleton5{
private static Singleton5 instance;
private Singleton5(){}
public static Singleton5 getInstance(){
if(instance==null){
synchronized (Singleton5.class) {
if(instance==null){
return instance;
}
}
}
return instance;
}
}
class Singleton6 {
private Singleton6(){}
private static class SingletonInstance{
private static final Singleton6 INSTANCE = new Singleton6();
}
public Singleton6 getInstance(){
return SingletonInstance.INSTANCE;
}
}
enum Singleton7{
INSTANCE;//属性
public void sayOk(){
System.out.println("ok~");
}
}
jdk源码
2.工厂模式
简单工厂模式
需求:一个披萨项目,要便于披萨类的扩展和维护。
- 披萨的种类很多(比如GreekPizz、CheesePizz等)。
- 披萨的制作有prepare,bake,cut,box.
- 完成披萨店订购功能。
2.1传统方式实现类图
编写OrderPizz.java去订购需要的各种Pizz.
public class OrderPizz {
public OrderPizz(){
Pizza pizza = null;
String orderType;//订购披萨的类型
do{
orderType = getType();
if(orderType.equals("greek")){
pizza = new GreekPizza();
pizza.setName("希腊披萨");
}else if(orderType.equals("cheese")){
pizza = new CheesePizza();
pizza.setName("奶酪披萨");
}else if(orderType.equals("peppr")){
pizza = new PepprPizza();
pizza.setName("胡椒披萨");
}else{
break;
}
//输出pizza的制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
}
2.2缺点和改进思路
- 违反了OCP原则,即对扩展开放,对修改关闭。比如我们这是新增加一个Pizza的种类(Pepper披萨),我们需要在OrderPizza类中添加一段代码。
- 改进:把创建Pizza对象封装到一个类中,这样我们有新的Pizza种类时。只需要修改该类就可以,其它有创建到Pizza对象的代码就不需要修改了。——>简单工厂模式
2.3基本介绍
1)简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)。
2)在软件开发中,当我们会用到大量的创建某种、某类或某批对象时,就会用到工厂模式。
3)简单工厂实现类图:
OrderPizza.java核心代码改写:
工厂方法模式
披萨项目新需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪披萨、北京的胡椒披萨 或 伦敦的奶酪披萨、伦敦的胡椒披萨。
思路1:使用简单工厂模式,创建不同的简单工厂,比如BJPizzaSimpleFactory、 LDPizzaSimpleFactory等。从当前案例来说是可以的,但是考虑到项目的规模,以及软件的可维护性,可扩展性并不是很好。
思路2:工厂方法模式。
2.4工厂方法模式介绍
- 工厂方法设计模式方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
- 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
2.5工厂方法模式类图
BJOrderPizza继承OrderPizza
抽象工厂模式
2.6 基本介绍
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类。
- 抽象工厂方法模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面,抽象工厂模式就是简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个工厂类变成了工厂簇,更利于代码的维护和扩展。
- 类图:
-代码:
2.7 工厂模式在JDK-Calender应用的源码
2.8 工厂模式小结
- 工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护 ,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)。
- 设计模式的依赖抽象原则:①创建实例对象时,不要直接new类,而是把这个new类的动作放到一个工厂的方法中,并返回。②不要让类继承具体的类而是继承抽象类或者是实现接口。③不要覆盖基类中已经实现的方法。
3.原型模式
需求:现在有一只Tom羊,姓名为:tom,年龄:1,颜色为:白色。编写程序创建和tom羊 属性完全相同的10只羊。
传统模式
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//传统的方法
Sheep sheep = new Sheep("tom", 1, "白色");
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep5 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
//....
System.out.println(sheep);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
System.out.println(sheep5);
//...
}
}
3.1缺点和改进思路
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率低。
- 总是需要重新初始化对象,而不是动态地获取对象运行的状态(就是程序运行时,对象值改变了,无法及时获取新的信息),不够灵活。
- 改进:Java中的Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要使用clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力——>原型模式。
3.2原型模式-基本介绍
- 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
- 原型模式是一种创建型设计模式允许一个对象再创建另外一个可制定的对象,无需知道如何创建的细节。
- 工作原理:对象.clone()。
- 类图:
原理结构图说明:
①Prototype:原型类,声明一个克隆自己的接口。
②ConcreatePrototype:具体的原型类,实现一个克隆自己的操作。
③Client:让一个原型对象克隆自己,从而创建一个新的对象。
3.3原型模式解决
要克隆的类需要实现Cloneable接口,并且重写clone方法,重写主要是处理一下异常。
public class Sheep implements Cloneable{
private String name;
private int age;
private String color;
//...
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sheep;
}
}
3.4原型模式在Spring框架中源码分析
- Spring中原型bean的创建,就是原型模式的应用。
- 代码分析+Debug源码。
3.5浅拷贝和深拷贝
1)浅拷贝:对于数据类型是基本数据类型和String类型的成员变量,浅拷贝会直接进行值传递,即复制一份给新的对象。对于其它引用类型的成员变量(数组,对象等)在浅拷贝时,则是进行引用传递,即将该成员变量的引用值(内存地址)复制一份给新的对象。对象.clone()方法默认就是浅拷贝。
2)深拷贝:基本数据类型的成员变量都是值传递。引用类型的成员变量则申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象(即对象里面可能还要引用类型的成员变量,也是深拷贝)。
3)深拷贝的两种方式:①重写clone方法。②通过序列化对象(推荐)。
4)深拷贝代码:
public class Sheep implements Serializable,Cloneable{
private String name;
private Sheep friend;
//方式一
@Override
protected Object clone() throws CloneNotSupportedException{
Sheep sheep = null;
//基本类型和String的克隆
sheep = (Sheep) super.clone();
//对引用类型单独处理。属性下面可能还有引用类型的成员变量
Sheep friend=null;
if(sheep.getFriend()!=null){
friend = (Sheep) sheep.getFriend().clone();
}
sheep.setFriend(friend);
return sheep;
}
//方式二
public Object deepClone(){
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//当前这个对象以对象流的方式输出
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Sheep sheep = (Sheep) ois.readObject();
return sheep;
} catch (Exception e) {
}finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
}
}
return null;
}
}
3.6原型模式注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能提高效率。
- 不用重新初始化对象,而是动态的获取对象的运行状态(对象运行过程中,属性可能发生变化,克隆时则是获取最新的状态)。
- 如果原始对象发生变化时(增加或者减少属性),其它克隆对象也会发生相应的变化,无需修改代码。
- 缺点:需要为每一个类配备一个克隆方法。这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点请同学们注意。
4.建造者模式
需求:
- 需要建房子:这一过程为打桩、砌墙、封顶。
- 房子有各种各样的,比如普通房、高楼、别墅。各种房子的建造过程一样,但建造要求不尽相同。
4.1传统方式
1.类图
2.传统问题优缺点和改进思路:
7. 1)设计的程序结构过于简单,没有设计缓存层对象,程序的扩展和维护不好。也就是说,这种设计方案,把产品(房子)和创建产品的过程(建造房子的流程)封装在一起,耦合性增强了。
8. 2)解决方案:将产品和创建产品的过程解耦。即使用建造者模式。
4.2建造者模式(将产品和创建产品分开)
1)建造者模式(Bulider Pattern)又叫生成器模式,适用于复杂对象的创建。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
2)建造者模式是一步一步创建一个复杂对象,它允许用户只通过指定复杂对象的类型和内容就可以构建他们。用户无需知道内部的具体构建细节。
4.3建造者模式的四个角色
- Product(产品角色):一个具体的产品对象。
- Builder(抽象建造者):创建一个Product对象的各个部件指定的 接口/抽象类。
- ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。
- Director(指挥者):构建一个Builder接口的对象。用于创建一个复杂对象。它主要有两个作用,一:隔离了客户与对象的生产过程;二:负责控制产品对象的生产过程。
类图:
4.4建房子代码解决
//产品
public class House{
private String baise;
private String wall;
private String roofed;
//getter/setter ...
}
//抽象的建造者
public abstract class HouseBulider {
protected House house = new House();
//抽象的建造方法
public abstract void buildHouse();
public abstract void buildWalls();
public abstract void buildroofed();
//建造好房子,将产品(房子)返回
public House buildHouse(){
return house;
}
}
//具体的建造者
public class HighBuliding extends HouseBulider {
@Override
public void buildBasic() {
house.setBaise("高楼的打地基100米");
System.out.println("高楼的打地基100米");
}
@Override
public void buildWalls() {
house.setWall("高楼的砌墙20cm");
System.out.println("高楼的砌墙20cm");
}
@Override
public void roofed() {
house.setRoofed("高楼的透明屋顶");
System.out.println("高楼的透明屋顶");
}
}
//指挥者
public class HouseDirector {
HouseBulider houseBulider = null;
//构造七传入,也可用setter方法
public HouseDirector(HouseBulider houseBulider){
this.houseBulider = houseBulider;
}
// 建造房子的流程,教给指挥者
public House constructHouse(){
houseBulider.buildBasic();
houseBulider.buildWalls();
houseBulider.roofed();
return houseBulider.buildHouse();
}
}
4.5建造者模式在JDK的应用源码分析
分析:
- Appendable 接口定义了多个append方法(抽象方法),即Appendable为抽象建造者,定义了抽象方法。
- AbstracrStringBuilder实现了Appendable接口方法,这里AbstracrStringBuilder已经是建造者,只是不能实例化。
- StringBuilder即充当了指挥者角色,同时也充当了具体的建造者,建造方法的实现是由AbstractStringBuilder完成。
4.6建造者模式的注意事项和细节
- 客户端不必知道产品内部组成细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体的建造者都是相互独立的,可以很方便的替换具体的建造者或增加新的具体建造者,符合开闭原则。
- 建造者模式创建的产品一般具有较多的共用点,其组成部分相似,如果产品之间的差异性很大 ,则不适合使用建造模式,因此范围受到一定的限制。
- 抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新的产品。
5.适配器模式
5.1适配器基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。
- 主要分为三类:类适配器模式,对象适配器模式,接口适配器模式。
工作原理:
5.2类适配器模式
介绍:Adapter类,通过继承src类,实现dst类接口,完成src->dst的适配。
注意事项:
- Java是单继承机制,所以类适配器需要继承src类这一点是一个缺点,因为这要求dst必须是接口,有一定的局限性。
- src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
- 由于其继承了src类,所以它可以重写src类的方法,使得Adapter的灵活性增强了。
5.3对象适配器模式
基本介绍:
- 思路和类适配器模式相同,只是将Adapter类作修改,不是继承src类而是持有src类的实例。
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承。
- 对象适配器模式是适配器模式常用的一种。
5.4接口适配器模式(缺省适配器模式)
基本介绍:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。适用于一个接口不想使用其所有的方法的情况。
5.4适配器模式在SpringMvc框架应用的源码分析
HandlerAdapter的子类使得每一种Controller有一种对应的适配器实现类,每种controller有不同的实现方式。controller相当于src
手写SpringMvc通过适配器设计模式获取到对应的Controller的实现类图
说明
-
Spring定义了一个适配器接口,使得每一个Controller有一种对应的适配器实现类。
-
适配器代替controller执行相应的方法。
-
扩展Controller 时,只需要增加一个适配器类就完成了SpringMvc的扩展。
手写SpringMvc通过适配器模式5.5适配器模式注意细节
-
类适配器:以类给到,在Adapter里,就是将src当成类,继承。
-
对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有。
-
接口适配器: 以接口给到,在Adapter里,将src作为一个接口,实现。
-
Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
-
在实际开发中,实现起来不拘泥于我们讲解的三种经典形式。
6.桥接模式
手机操作问题:现在对不同手机类型的不同品牌实现操作编码(比如:开机、关机、上网、打电话)。
6.1传统方式
存在问题于解决方案:
- 扩展性问题(类爆炸),如果我们再增加手机样式(旋转式),就需要增加各个品牌手机(违反了单一职责原则),如果增加手机品牌,同样需要在各个样式下面加。
- 解决方案-使用桥接模式。
6.2桥接模式(两个独立变化的维度,抽象,实现)
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。解决类爆炸。
- 桥接模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象与行为实现分离开来,从而可以保持各部分的独立性以及他们的功能扩展。
- 桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量。
桥接模式解决:
6.3桥接模式在JDBC的源码刨析
(1)Jdbc 的 Driver 接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当作实现接口类。
DriverManger中通过getConnecction()方法聚合了Connection接口。
每种数据库都有对应的Driver类,Driver类里面又聚合了DriverManger类。
6.4桥接模式注意事项和细节
- 实现了抽象和实现部分的分离,提高灵活性。
- 桥接模式替代多层继承方案,解决类爆炸问题,降低系统管理和维护成本。
- 桥接模式要求正确识别出系统中两个独立的变化维度,因此有局限性,即需要这样的应用场景。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
- 常见的应用场景:
1.JDBC驱动。
2.银行转账系统:
转账分类:网上转账,柜台转账,AMT转账。
转账用户类型:普通用户,银卡用户,金卡用户。
3.消息管理:
消息类型:即时消息,延时消息。
消息分类:手机短信,邮件信息,QQ消息。
7.装饰者模式
7.1需求:星巴克咖啡订单项目
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)。
- 调料:Milk,Soy(豆浆),Chocolate。
- 要求在扩展新的咖啡种类时,具有良好的扩展性和维护性。
- 使用OO的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合。
Espress && Milk就是单品咖啡+调料。
问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,出现类爆炸。
方案2问题分析:
- 可以控制类的数量,不至于造成很多的类。
- 在增加或者删除调料种类时,代码的维护量很大。
- 考虑到用户可以添加多份调料,可以将hasMilk返回一个int.
- 考虑使用 装饰者模式。
7.2装饰者模式介绍
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,体现了开闭原则(ocp)。
Coffee和Decoratr都继承了Drink,并且Decoratr这聚合了Drink。即继承(实现)+组合。
7.3装饰者模式在JDK应用的源码分析
8.组合模式
8.1需求
8.2组合模式介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树形结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分和整体层次。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。
组合模式三种角色:
1.Component是接口/抽象类,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件。
2.Composite,组合可以操作子节点和叶子,但可能不具有叶子的某些行为。
3.Leaf,叶子节点没有孩子,不能操作孩子,但其定义组合内元素的行为。
解决类图
8.3组合模式在JDK集合源码分析
Map、AbstractMap充当compoent,HashMap充当composite,HashMap中的内部类Node充当叶子节点。
9.外观模式
外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用。
外观模式在MyBatis的源码分析。
上面对应的类图:
10.享元模式
10.1享元模式基本结束
介绍:享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象。享元模式有内部状态(不经常变化的,可以共享的)和外部状态(经常变化的,不能共享的)。
作用:解决重复对象的内存浪费问题。
应用场景:经典的应用场景就是池技术了,String常量池,数据库连接池,缓冲池等都是享元模式的应用场景。
FlyWeightFactory:是享元工厂角色,它是关键字 key 来管理具体享元;客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。
享元模式解决网站展现问题的类图:
根据网站的类型,返回一个网站,如果没有就创建一个网站,并放入到池中,并返回。
10.2享元模式在JDK-Interger的应用源码分析
11.代理模式
11.1代理模式基本介绍
代理模式:通过代理对象访问目标对象。好处:可以在目标对象实现的基础上,增强额外的功能操作。
应用场景:代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
分类:静态代理(基于接口)和动态代理。而动态代理又分为两种,一种是JDK代理或称为接口代理,另一种是Cglib代理(在内存中动态的创建对象,不需要实现接口)。
示意图
#### 11.1静态代理
类图:
11.2动态代理(jdk)
动态代理特的特点:
- 字节码随用随创建,随用随加载。
- 它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
- 装饰者模式就是静态代理的一种体现。
基本介绍:
- 不需要实现接口,但是目标对象要实现接口。否则不能用动态代理。
- 代理对象的生成,是利用JDK的API(反射),动态的在内存中构建代理对象。
- 代理类所在包:java.lang.reflect.Proxy。
- 需要实现Proxy.newProxyInstance()方法。
类图:
public class TeacherDapProxy {
//维护一个目标对象
private Object target;
public TeacherDapProxy(Object target){
this.target = target;
}
//给目标对象生成一个代理对象
public Object getProxyInstance(){
/**
* Proxy.newProxyInstance(ClassLoader loader, 目标对象使用的类加载器
Class<?>[] interfaces,目标对象实现的接口类型
InvocationHandler h)事情处理,执行目标对象的方法,会触发事情处理方法
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
/**
* proxy:代理对象的引用
* method:代理的方法对象
* args:方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法前做的事。。。");
Object res = method.invoke(target, args);
System.out.println("方法后做的事。。。");
return res;
}
});
}
}
11.3动态代理(cglib)
- Cglib代理也叫子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。被代理类不能被final修饰。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP框架使用,例如Sping AOP,实现方法拦截。
- 在AOP编程中如何选择代理模式:
目标对象需要实现接口:JDK代理。
目标对象不需要实现接口,Cglib代理。 - Cglib包的底层是通过使用字节码处理框架ASM来转换字节码生成新的类。
类图:
public class TeacherDapProxy implements MethodInterceptor{
//维护一个目标对象
private Object target;
public TeacherDapProxy(Object target){
this.target = target;
}
//给目标对象生成一个代理对象
public Object getProxyInstance(){
//1.创建一个工具类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(target.getClass());
//3.设置回掉函数
enhancer.setCallback(this);
//4.创建子类对象,即代理对象
return enhancer.create();
}
//重写 intercept方法 ,会调用目标对象的方法.通过拦截器的方式实现代理
//前三个参数和基于接口的动态代理是一样的。
//methodProxy:当前执行方法的代理对象。
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始");
Object res = method.invoke(target, args);
System.out.println("cglib代理结束");
return res;
}
}
第二种写法:
public class Client {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDao();
TeacherDao proxy = (TeacherDao) Enhancer.create(teacherDao.getClass(),new MethodInterceptor(){
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("cglib代理前");
Object res = method.invoke(teacherDao, args);
System.out.println("cglib代理后");
return res;
}
});
proxy.teach();
}
}
12.模板方法模式
12.1模板方法模式基本介绍
- 模板方法模式又叫模板模式,指在一个抽象类中公开定义了它执行的方法模板。子类可以根据需要重写方法,但调用将以抽象类中定义的模板方法。
- 简单的说,模板方法模式定义了一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类不改变算法的结构,就可以重新定义某些特定的步骤。
原理类图:
类图说明(角色及职责)
- AbstractClass抽象类,类中实现了模板方法(template),定义了算法的骨架,具体子类需要实现其它抽象方法。
- ConcreteClass实现抽象方法operation2,3,4。完成算法中特定子类步骤。
12.2模板方法模式解决豆浆制作问题
类图:
部分代码:
12.3模板方法模式的钩子方法
12.4模板方法模式在Spring框架应用源码分析
对应类图:
模板方法模式还可以用于统计某段代码执行的时间。
执行代码前打印时间,执行代码,执行代码后打印时间
13.命令模式
13.1需求
13.2命令模式基本介绍
- 命令模式:向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道请求的操作是哪个。我们只需在程序运行时指定具体的请求接受者即可。
- 命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间调用关系更加灵活,实现解耦。
- 在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命令),同时命令模式也支持撤销操作。
- 通俗易懂的理解:将军发布命令,士兵去执行,那个士兵去执行,将军不用管。其中几个角色:将军(命令发布者),士兵(命令执行者),命令(连接将军和士兵)。
Invoker-调用者(将军),Receiver-被调用者(士兵),MyCommand-命令,实现了Command接口,持有接收者对象。
类图:
13.3模命令模式解决智能生活项目
QueryStatementCallback是 JdbcTemplate的内部类。QueryStatementCallback该类中的doInStatementCallback方法中的Statement就相当于接收者。
请求者聚合了命令,命令聚合了接收者。
14.访问者模式
在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;
访问者模式原理类图:
访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。
扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
访问者(Visitor)模式的主要缺点如下。
增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
14.迭代器模式
原理类图:
迭代器模式主要包含以下角色。
抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
案例类图:
JDK类图分析: