目录
一、概况总体来说设计模式分为三大类:
(1)创建型模式,共五种:
(2)结构型模式,共七种:
二、设计模式的六大原则
1、开闭原则(Open Close Principle)
2、里氏代换原则(Liskov Substitution Principle)
3、依赖倒转原则(Dependence Inversion Principle)
4、接口隔离原则(Interface Segregation Principle)
5、迪米特法则(最少知道原则)(Demeter Principle)
6、合成复用原则(Composite Reuse Principle)
三.创建型模式(大概和大家介绍三种):
1.原型模式(Prototype)
1.定义:
2.类型:
3.类图:
4.简介:
5.代码演示:
6.运行效果图
7.结论:
3.2.单例模式(Singleton)
1.简介
2、单例模式:模拟场景
3.优缺点
4.- 分类
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步代码块)
- 懒汉式(线程安全,同步方法)
- 双重检查
- 静态内部类
- 枚举
5、单例模式:总结
3.3工厂模式(Factory)
1.简介
2.工厂模式的优点:
3.工厂模式的缺点:
4.简单工厂模式类图结构:
5.- 案例
6.代码演示(为使用工厂模式之前):
7.运行效果图:
8.结论:
9.运行效果图:
一、Singleton,单例模式 : 保证一个类只有一个实例,并提供一个访问它的全局访问点
二、Abstract Factory,抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类
三、Factory Method,工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到了子类
四、Builder,建造模式:将一个复杂对象的构建与他的表示相分离,使得同样的构建过程可以创建不同的表示
五、Prototype,原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象
六、Iterator,迭代器模式:提供一个方法顺序访问一个聚合对象的各个元素,而又不需要暴露该对象的内部表示。
七、Observer,观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知自动更新。
八、Template Method,模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法得某些特定步骤。
九、Command,命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队和记录请求日志,以及支持可撤销的操作。
十、State,状态模式:允许对象在其内部状态改变时改变他的行为。对象看起来似乎改变了他的类。
十一、Strategy,策略模式:定义一系列的算法,把他们一个个封装起来,并使他们可以互相替换,本模式使得算法可以独立于使用它们的客户。
十二、China of Responsibility,职责链模式:使多个对象都有机会处理请求,从而避免请求的送发者和接收者之间的耦合关系
十三、Mediator,中介者模式:用一个中介对象封装一些列的对象交互。
十四、Visitor,访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这个元素的新操作。
十五、Interpreter,解释器模式:给定一个语言,定义他的文法的一个表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
十六、Memento,备忘录模式:在不破坏对象的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
(3)行为型模式,共十一种:
十七、Composite,组合模式:将对象组合成树形结构以表示部分整体的关系,Composite使得用户对单个对象和组合对象的使用具有一致性。
十八、Facade,外观模式:为子系统中的一组接口提供一致的界面,fa?ade提供了一高层接口,这个接口使得子系统更容易使用。
十九、Proxy,代理模式:为其他对象提供一种代理以控制对这个对象的访问
二十、Adapter,适配器模式:将一类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。
二十一、Decrator,装饰模式:动态地给一个对象增加一些额外的职责,就增加的功能来说,Decorator模式相比生成子类更加灵活。
二十二、Bridge,桥模式:将抽象部分与它的实现部分相分离,使他们可以独立的变化。
二十三、Flyweight,享元模式
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。
其官方描述比较抽象,可自行百度。实际上可以这样理解:(1)子类的能力必须大于等于父类,即父类可以使用的方法,子类都可以使用。(2)返回值也是同样的道理。假设一个父类方法返回一个List,子类返回一个ArrayList,这当然可以。如果父类方法返回一个ArrayList,子类返回一个List,就说不通了。这里子类返回值的能力是比父类小的。(3)还有抛出异常的情况。任何子类方法可以声明抛出父类方法声明异常的子类。
而不能声明抛出父类没有声明的异常。
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
原则是尽量使用合成/聚合的方式,而不是使用继承。
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
创建类模式
用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
使用前
此时我要一只名为杰瑞的绵羊,其它绵羊属性与杰西一致;
那么按照这种设计,只能这么创建所需的绵羊
这种方式创建,目前只有两个属性问题不大,如果绵羊类有十几二十甚至更多的属性,那么是非常不方便的
package com.javaxl.design.prototype.before;
public class Sheep {
private String name;
private String sex;
public Sheep(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
/**
* @company
* @create 2020-02-22 10:46
*
* 将一只名字为杰克、性别为母的绵羊克隆10份;
* 要求每只绵羊的属性、性别都一致;
*
* 弊端:无法将当前的状态进行复制
*/
public class Client {
public static void main(String[] args) {
Sheep sheep1 = new Sheep("杰西", "母");
Sheep sheep2 = new Sheep("杰西", "母");
Sheep sheep3 = new Sheep("杰西", "母");
Sheep sheep4 = new Sheep("杰西", "母");
Sheep sheep5 = new Sheep("杰西", "母");
Sheep sheep6 = new Sheep("杰西", "母");
Sheep sheep7 = new Sheep("杰西", "母");
Sheep sheep8 = new Sheep("杰西", "母");
Sheep sheep9 = new Sheep("杰西", "母");
Sheep sheep10 = new Sheep("杰西", "母");
Sheep sheep11 = new Sheep("杰瑞", "母");
}
}
使用后
package daom;
/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create 2020-02-22 10:45
*
* 使用原型设计模式进行设计
*/
public class Sheep implements Cloneable{
private String name;
private String sex;
public Sheep(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
System.out.println("被克隆了...");
return obj;
}
}
package daom;
public class Client {
public static void main(String[] args) throws Exception{
Sheep sheep1 = new Sheep("杰西", "母");
Sheep sheep2 = (Sheep) sheep1.clone();
Sheep sheep3 = (Sheep) sheep1.clone();
Sheep sheep4 = (Sheep) sheep1.clone();
Sheep sheep5 = (Sheep) sheep1.clone();
Sheep sheep6 = (Sheep) sheep1.clone();
Sheep sheep7 = (Sheep) sheep1.clone();
Sheep sheep8 = (Sheep) sheep1.clone();
Sheep sheep9 = (Sheep) sheep1.clone();
Sheep sheep10 = (Sheep) sheep1.clone();
System.out.println(sheep1);
System.out.println(sheep2);
// 此时我要一只名为杰瑞的绵羊,其它绵羊属性与杰西一致;
// 按照原型设计模式,调用方Client类无需查找杰西相同部分的属性,只需变动差异部分属性进行克隆即可;
// 这种设计,目前只有两个属性使用起来感觉没多大区别,如果绵羊类有十几二十甚至更多的属性,那么感觉非常明显
sheep1.setName("杰瑞");//其它的属性不需要去关注
Sheep sheep11 = (Sheep) sheep1.clone();
System.out.println(sheep11);
}
}
结论:推荐使用深拷贝
- 注意事项和细节
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
- 不用重新初始化对象,而是动态地获得对象运行时的状态
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码 缺点:
1、需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则
2、实现深克隆的时候可能需要比较复杂的代码
- 单例模式
- 需要保证一个类只有一个实例。
- 哪怕多线程同时访问,而且需要提供一个全局访问此实例的点
- 解决:一个全局使用的类,被频繁地创建与销毁,从而提升代码的总体性能。
Spring
中一个单例模式 Bean
的生成和使用
单例模式的优点:
① 单例模式可以保证内存里只有一个实例,减少了内存的开销。
② 可以避免对资源的多重占用。
③ 单例模式设置全局访问点,可以优化和共享资源的访问。单例模式的缺点:
① 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
② 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
③ 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
package com.chaiyang.demo;
/**
* 饿汉式(静态常量)
*/
public class DBAccess {
// 构造器私有化,避免外部创建对象
private DBAccess() {
}
// static修饰,保障其能够被静态方法访问
private final static DBAccess dbAccess = new DBAccess();
// 外部直接调用静态方法实例化对象
public static DBAccess getInstance() {
return dbAccess;
}
}
饿汉式优点:饿汉式在类加载时,就已经创建了实例对象,故不存在线程安不安全的问题。
饿汉式缺点:类加载时就创建了实例对象,比如只是调用里面某个static方法,这个时候就会创建了该实例对象了,有可能一直都不需要getInstance,因此这样会造成资源浪费。
/**
* 饿汉式(静态代码块)
*/
package com.chaiyang.demo;
public class DBAccess2 {
private DBAccess2() {
}
private static DBAccess2 dbAccess = null;
static {
dbAccess = new DBAccess2();
}
public static DBAccess2 getInstance() {
return dbAccess;
}
}
这种方式和第一种使用静态常量的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。
/**
* 懒汉式(线程不安全)
*/
package com.chaiyang.demo;
public class DBAccess3 {
private DBAccess3() {
}
private static DBAccess3 dbAccess = null;
public static DBAccess3 getInstance() {
if (dbAccess == null) {
dbAccess = new DBAccess3();
}
return dbAccess;
}
}
虽然起到了懒加载的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了 if 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在实际开发中,不要使用这种方式。
/**
* 懒汉式(同步代码块)
*/
package com.chaiyang.demo;
public class DBAccess4 {
private DBAccess4() {
}
private static DBAccess4 dbAccess = null;
public static DBAccess4 getInstance() {
synchronized (DBAccess4.class) {
if (dbAccess == null) {
dbAccess = new DBAccess4();
}
}
return dbAccess;
}
}
/**
* 懒汉式(线程安全,同步方法)
*/
package com.chaiyang.demo;
public class DBAccess5 {
private DBAccess5() {
}
private static DBAccess5 dbAccess = null;
public synchronized static DBAccess5 getInstance() {
if (dbAccess == null) {
dbAccess = new DBAccess5();
}
return dbAccess;
}
}
虽然解决了线程安全问题,但是效率太低了,每个线程在想获得类的实例时候,执行 getlnstance 方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低,在实际开发中,不推荐使用这种方式
/**
* 双重检查
*/
package com.chaiyang.demo;
public class DBAccess6 {
private DBAccess6() {
}
private static DBAccess6 dbAccess = null;
public static DBAccess6 getInstance() {
if (dbAccess == null) {
synchronized (DBAccess6.class) {
if (dbAccess == null) {
dbAccess = new DBAccess6();
}
}
}
return dbAccess;
// return new DBAccess6();
}
}
双重检查是同步代码块的升级版,在多线程开发中常使用到的,如代码中所示,我们进行了两次 if 检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断 if ,直接 return 实例化对象,也避免的反复进行方法同步。线程安全;延迟加载;效率较高。在实际开发中,推荐使用。
/**
* 静态内部类
*/
package com.chaiyang.demo;
public class DBAccess7 {
private DBAccess7() {
}
private static class DBAccess7Instance{
private static DBAccess7 dbAccess = new DBAccess7();
}
public static DBAccess7 getInstance() {
return DBAccess7Instance.dbAccess;
}
}
这种方式采用了类装载的机制来保证初始化实例时只有一个线程。静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getlnstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。类的静态属性只会在第一次加载类的时候初始化,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。避免了线程不安全,利用静态内部类特点实现延迟加载,效率高,推荐使用。
/**
* 枚举
*/
package com.chaiyang.demo;
public enum DBAccess8{
DBACCESS;
public static DBAccess8 getInstance() {
return DBAccess8.DBACCESS;
}
}
借助 JDK 1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。
单例模式在各种实现上用到了 Java 的基本功,包括
懒汉模式、饿汉模式、线程是否安全、静态类、内部类、加锁、串行化。
确保此类是全局可用的,则不需要懒汉模式,那么直接创建并给外部调用即可
但如果有很多的类,有些需要在用户触发一定的条件后才显示,那么一定要用懒汉模式
对于线程的安全,可以按需选择
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个协议,只要扩展一个协议类就可以。
3、屏蔽协议的具体实现,调用者只关心协议的接口。
工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
使用工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度。
系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂。
工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
SimpleFactory:简单工厂类,简单工厂模式的核心,它负责实现创建所有实例。简单工厂创建产品的方法可以被外界直接调用来创建所需的产品对象。
IProduct:抽象产品类,简单工厂模式创建的所有对象的父类,它负责描述所有实例所共有的公共函数接口。
ConcreteProduct:具体产品类,是简单工厂模式的创建目标。
需求:一个披萨制作的项目,要求该项目易于扩展维护;
1、能够生产出美式披萨、中式披萨...
2、披萨制作过程包含原材料准备、烘培、切割、打包
3、可生成披萨订单
package com.chaiyang.demo;
public interface Pizza {
void pre();
void bake();
void cut();
void box();
}
/**
* 中式披萨
*/
class ChinesePizza implements Pizza {
public ChinesePizza() {
this.pre();
this.bake();
this.cut();
this.box();
}
@Override
public void pre() {
System.out.println("中式披萨材料准备...");
}
@Override
public void bake() {
System.out.println("中式披萨烘培...");
}
@Override
public void cut() {
System.out.println("中式披萨的切片...");
}
@Override
public void box() {
System.out.println("中式披萨包装盒包装");
}
}
/**
* 美式披萨
*/
class AmericaPizza implements Pizza {
public AmericaPizza() {
this.pre();
this.bake();
this.cut();
this.box();
}
@Override
public void pre() {
System.out.println("美式 披萨材料准备...");
}
@Override
public void bake() {
System.out.println("美式 披萨烘培...");
}
@Override
public void cut() {
System.out.println("美式 披萨的切片...");
}
@Override
public void box() {
System.out.println("美式 披萨包装盒包装");
}
}
class JapanPizza implements Pizza {
public JapanPizza() {
this.pre();
this.bake();
this.cut();
this.box();
}
@Override
public void pre() {
System.out.println("日式 披萨材料准备...");
}
@Override
public void bake() {
System.out.println("日式 披萨烘培...");
}
@Override
public void cut() {
System.out.println("日式 披萨的切片...");
}
@Override
public void box() {
System.out.println("日式 披萨包装盒包装");
}
}
package com.chaiyang.demo;
public class PizzaFactory {
public static Pizza createPizza(int no){
switch (no){
case 1:
return new ChinesePizza();
case 2:
return new AmericaPizza();
case 3:
return new JapanPizza();
}
return null;
}
}
package com.chaiyang.demo;
public class Client {
public static void main(String[] args) {
Pizza chinaPizza = PizzaFactory.createPizza(1);
System.out.println("=============================");
Pizza americaPizza = PizzaFactory.createPizza(2);
System.out.println("=============================");
Pizza japanPizza = PizzaFactory.createPizza(3);
}
}
没有使用工厂模式之前运行的时候它的每一个步骤全部执行了一次,如披萨制作过程包含原材料准备、烘培、切割、打包,但是有的时候不用辣么多的步骤,这个时候就要用到工厂模式了
代码演示(使用工厂模式):
package com.chaiyang.demo;
public interface Pizza {
void pre();
void bake();
void cut();
void box();
}
/**
* 中式披萨
*/
class ChinesePizza implements Pizza {
@Override
public void pre() {
System.out.println("中式披萨材料准备...");
}
@Override
public void bake() {
System.out.println("中式披萨烘培...");
}
@Override
public void cut() {
System.out.println("中式披萨的切片...");
}
@Override
public void box() {
System.out.println("中式披萨包装盒包装");
}
}
/**
* 美式披萨
*/
class AmericaPizza implements Pizza {
@Override
public void pre() {
System.out.println("美式 披萨材料准备...");
}
@Override
public void bake() {
System.out.println("美式 披萨烘培...");
}
@Override
public void cut() {
System.out.println("美式 披萨的切片...");
}
@Override
public void box() {
System.out.println("美式 披萨包装盒包装");
}
}
class JapanPizza implements Pizza {
@Override
public void pre() {
System.out.println("日式 披萨材料准备...");
}
@Override
public void bake() {
System.out.println("日式 披萨烘培...");
}
@Override
public void cut() {
System.out.println("日式 披萨的切片...");
}
@Override
public void box() {
System.out.println("日式 披萨包装盒包装");
}
}
package com.chaiyang.demo;
public class PizzaFactory {
public static Pizza createPizza(int no){
Pizza pizza = null;
switch (no){
case 1:
pizza = new ChinesePizza();
break;
case 2:
pizza = new AmericaPizza();
break;
case 3:
pizza = new JapanPizza();
break;
}
if (pizza == null){
System.out.println("没有此披萨订购服务");
}else {
pizza.pre();
pizza.bake();
pizza.cut();
pizza.box();
}
return pizza;
}
}
package com.chaiyang.demo;
public class Client {
public static void main(String[] args) {
Pizza chinaPizza = PizzaFactory.createPizza(1);
System.out.println("=============================");
Pizza americaPizza = PizzaFactory.createPizza(2);
System.out.println("=============================");
Pizza japanPizza = PizzaFactory.createPizza(3);
System.out.println("=============================");
Pizza otherPizza = PizzaFactory.createPizza(4);
}
}
使用前后代码对比:
前者将对象初始化的工作交给了对象的构造函数完成;
后者将初始化的过程交给了工厂类完成;
这么做的好处在于解耦自身对象实例化和对象初始化动作,还有一个因素在于构造函数的初始化会影响到子类;
- 注意事项及细节
将对象的初始化交给工厂类
构造函数初始化会影响到子类,耦合度过高