目录
1.设计模式概述
1.什么是设计模式
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。
它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
1995年,GoF (Gang of Four,四人组/*)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF设计模式」
2.学习设计模式的意义
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
- 正确使用设计模式具有以下优点:
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
3.23种设计模式
按照设计模式的作用可分为三种模式,创建型模式、结构型模式、行为型模式
设计模式类型 | 设计模式 |
---|---|
创建型模式 | 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式 |
结构型模式 | 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式 |
行为型模式 | 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式 |
4.七大设计原则
七大原则 | 解释 |
---|---|
开闭原则 | 对扩展开放,对修改关闭 |
里氏替换原则 | 在继承关系中子类可以拓展方法,不能修改父类原有方法的功能,降低需求变更带来的风险 |
依赖倒置原则 | 要面向接口编程,不要面向实现编程。高层模块不直接依赖低层模块,二者依赖其抽象 |
单一职责原则 | 控制类的粒度大小、将对象解耦、提高其内聚性。对于类/接口/方法,负责的功能单一 |
接口隔离原则 | 不使用单一的总接口 使用多个专门职责的接口,接口间产生隔离 |
迪米特法则 | 只与你的直接朋友交谈,不跟"陌生人”说话。其中一个类需要调用另一类的某一个方法的话,可以通过第三者转发这个调用。降低类与类之间的耦合 |
合成复用原则 | 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。 |
2.创建者模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
1.单例模式
概述:保证一个类仅有一个实例,并提供一个访问它的全局访问点
优点:
- 1.在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
应用场景:WEB 中的计数器
主要解决:一个全局使用的类频繁地创建与销毁。
关键代码:构造函数是私有的,自己创建自己的唯一实例,公有的静态的方法
饿汉式
一上来就把所有的东西加载进来,非常浪费空间
类加载时创建(不管使不使用都创建),天生线程安全
生命周期长
/**
* 饿汉式单例
*/
public class Hungry {
/**
* 可能会浪费空间
*/
private byte[] data1=new byte[1024*1024];
private byte[] data2=new byte[1024*1024];
private byte[] data3=new byte[1024*1024];
private byte[] data4=new byte[1024*1024];
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
懒汉式
第一次调用才初始化,避免内存浪费。
单线程安全,多线程不安全
生命周期短
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这里可以给他加锁完成线程安全,但是就降低了效率
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
DCL懒汉式(双重校验(判定)锁)
这里new对象的时候会有原子性不一致的问题,就导致可能出现指令重排的问题,给LazyMan2的类对象加一个volatile保证指令重排的问题
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类
线程安全,功能和双重校验锁一样
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
但是这时候出现了问题,反射可以破坏单例模式
枚举
它更简洁,自动支持序列化机制,绝对防止多次实例化。可以防止反射来破坏单例模式,因为反射破坏不了枚举类
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
2.工厂模式
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
- 三种模式:
- 简单工厂模式
- 用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 工厂方法模式
- 用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式
- 围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
- 简单工厂模式
- 小结:
- 简单工厂模式(静态工厂模式)
- 虽然某种程度上不符合设计原则,但实际使用最多!
- 工厂方法模式
- 不修改已有类的前提下,通过增加新的工厂类实现扩展。
- 抽象工厂模式
- 不可以增加产品,可以增加产品族!
- 简单工厂模式(静态工厂模式)
应用场景:JDBC中的Connection对象的获取、Spring中IOC容器创建管理bean对象、反射中Class对象的newInstance方法
简单工厂模式(静态工厂模式)
结构:
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现或者继承抽象产品的子类.
- 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品。
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了假改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改厂类的代码,违背了“开闭原则”。
案例:
public interface Car {
void name();
}
class Wuling implements Car {
@Override
public void name() {
System.out.println("五菱");
}
}
class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
class CarFactory {
public static Car getCar(String car) {
if ("五菱".equals(car)) {
return new Wuling();
} else if ("特斯拉".equals(car)) {
return new Tesla();
} else {
return null;
}
}
}
class Consumer {
public static void main(String[] args) {
//传统方式
//Car wuling = new Wuling();
//Car tesla = new Tesla();
//工厂模式
Car car = CarFactory.getCar("五菱");
Car car1 = CarFactory.getCar("特斯拉");
car.name();
car1.name();
}
}
这时我想要在加一辆车,那么车工厂就要增加一个大众类型的车,按照类型去寻找车
工厂方法
优化:针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
概述:定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使-个产品类的实例化延迟到其工厂的子类。
结构:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
案例:
工厂方法,增加一层工厂的约束,对工厂去管理,要什么车就去什么工厂,然后工厂帮你找到车
从下面的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。
工厂方法模式是简单工厂模式的进一步抽象。 由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
//抽象产品
public interface Car {
void name();
}
//具体产品
class Wuling implements Car {
@Override
public void name() {
System.out.println("五菱");
}
}
//具体产品
class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
}
//抽象工厂
interface CarFactory{
Car getCar();
}
//具体工厂
class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
//具体工厂
class WulingFactory implements CarFactory{
@Override
public Car getCar() {
return new Wuling();
}
}
class Consumer {
public static void main(String[] args) {
//传统方式
//Car wuling = new Wuling();
//Car tesla = new Tesla();
//工厂模式
//Car car = CarFactory.getCar("五菱");
//Car car1 = CarFactory.getCar("特斯拉");
//car.name();
//car1.name();
Car car = new WulingFactory().getCar();
Car car1 = new TeslaFactory().getCar();
car1.name();
car.name();
}
}
3.抽象工厂模式
概念:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
结构:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteRroduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。
注意事项:产品族难扩展,产品等级易扩展。
//手机接口:抽象产品
public interface IphoneProduct {
void start();
void shutdown();
void callup();
void sendSMS();
}
//路由器接口:抽象产品
public interface IRouterProduct {
void start();
void shutdown();
void openwife();
void setting();
}
//华为手机:具体产品
public class HuaweiPhone implements IphoneProduct{
@Override
public void start() {
System.out.println("华为手机开机");
}
@Override
public void shutdown() {
System.out.println("华为手机关机");
}
@Override
public void callup() {
System.out.println("华为手机打电话");
}
@Override
public void sendSMS() {
System.out.println("华为手机发短信");
}
}
//华为路由器:具体产品
public class HuaweiRouter implements IRouterProduct{
@Override
public void start() {
System.out.println("开启华为路由器");
}
@Override
public void shutdown() {
System.out.println("关闭华为路由器");
}
@Override
public void openwife() {
System.out.println("开启华为wifi");
}
@Override
public void setting() {
System.out.println("打开华为设置");
}
}
//小米手机:具体产品
public class XiaomiPhone implements IphoneProduct{
@Override
public void shutdown() {
System.out.println("关闭小米手机");
}
@Override
public void callup() {
System.out.println("小米手机打电话");
}
@Override
public void sendSMS() {
System.out.println("小米手机发短信");
}
@Override
public void start() {
System.out.println("开启小米手机");
}
}
//小米路由器:具体产品
public class XiaomiRouter implements IRouterProduct{
@Override
public void start() {
System.out.println("开启小米路由器");
}
@Override
public void shutdown() {
System.out.println("关闭小米路由器");
}
@Override
public void openwife() {
System.out.println("开启小米wifi");
}
@Override
public void setting() {
System.out.println("开启小米设置");
}
}
//生产路由器和手机的工厂:抽象工厂
public interface IProductFactory {
//生产手机
IphoneProduct iphoneProduct();
//生产路由器
IRouterProduct irouterproduct();
}
//生产华为系列路由器和手机的工厂:具体工厂
public class HuaweiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct() {
return new HuaweiPhone();
}
@Override
public IRouterProduct irouterproduct() {
return new HuaweiRouter();
}
}
//生产小米系列路由器和手机的工厂:具体工厂
public class XiaomiFactory implements IProductFactory{
@Override
public IphoneProduct iphoneProduct() {
return new XiaomiPhone();
}
@Override
public IRouterProduct irouterproduct() {
return new XiaomiRouter();
}
}
//主机,去找一个商品直接去该系列的工厂去寻找并使用
public class Clinet {
public static void main(String[] args) {
System.out.println("==========小米系列产品==========");
//小米工厂
XiaomiFactory xiaomiFactory = new XiaomiFactory();
IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct();
iphoneProduct.start();
iphoneProduct.shutdown();
System.out.println("==========华为系列产品==========");
//小米工厂
HuaweiFactory huaweiFactory = new HuaweiFactory();
iphoneProduct = huaweiFactory.iphoneProduct();
iphoneProduct.start();
iphoneProduct.shutdown();
}
}
4.建造者模式
概念:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
结构:
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建, 并不涉及具体的对象部件的创建。
- 具体建造者类(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提*品的实例。
- 产品类(Product):要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦, 使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
与工厂模式的区别是:建造者模式更加关注与零件装配的顺序
建造者与抽象工厂模式的比较:
- 与抽象工厂 模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一 个复杂对象, 返回一个完整的对象。
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车!
案例:
//抽象建造者
public abstract class Builder {
abstract void builderA();//地基
abstract void builderB();//钢筋水泥
abstract void builderC();//铺电线
abstract void builderD();//粉刷
//完工:得到产品
abstract Product getproduct();
}
//产品类:房子
public class Product {
private String builderA;
private String builderB;
private String builderC;
private String builderD;
//get set tostring
}
//具体建造者:工人
public class Worker extends Builder{
private Product product;
public Worker() {
product = new Product();
}
@Override
void builderA() {
product.setBuilderA("地基");
System.out.println("地基");
}
@Override
void builderB() {
product.setBuilderB("钢筋水泥");
System.out.println("钢筋水泥");
}
@Override
void builderC() {
product.setBuilderC("铺电线");
System.out.println("铺电线");
}
@Override
void builderD() {
product.setBuilderD("粉刷");
System.out.println("粉刷");
}
@Override
Product getproduct() {
return product;
}
}
//指挥者:核心。负责构建一个工程,工程如何构建,由他决定。
public class Director {
//指挥工人按照顺序建房子
public Product build(Builder builder){
builder.builderA();
builder.builderB();
builder.builderD();
builder.builderC();
return builder.getproduct();
}
}
public class Test {
public static void main(String[] args) {
Director director = new Director();
Product build = director.build(new Worker());
System.out.println(build);
}
}
5.原型模式
概述:通过复制现有的实例来创建新的实例。利用已有的一个原型对象,快速地生成和原型对象一样的实例。用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
结构:
- 抽象原型类:规定了具体原型对象必须实现的的clone() 方法。
- 具体原型类:实现抽象原型类的clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的clone() 方法来复制新的对象。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 4、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
主要解决:在运行期建立和删除原型。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
案例:
public class Video implements Cloneable{
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//有参无参、getset、tostring
}
public class Blllllllllli {
public static void main(String[] args) throws CloneNotSupportedException {
//原型对象
Date date = new Date();
Video v1 = new Video("雙馬", date);
System.out.println("v1== " + v1);
System.out.println("v1.hash== " + v1.hashCode());
//我们这里想要一个和v1一样的对象
//有的人会想,在new一个呗,new的对象是初始化的,就需要将所有v1对象添加的属性全部重新操作一遍,
Video v2 = new Video("雙馬", date);
//通过克隆的方式,可以直接制造出一个和v1一样的副本,但是clone是一个浅拷贝操作,指向并没有变
Video v3 = (Video) v1.clone();
//这里我们会想那怎么看出来一样呢,进行输出和hash
System.out.println("v2== " + v2);
System.out.println("v2.hash== " + v2.hashCode());
System.out.println("v3== " + v3);
System.out.println("v3.hash== " + v3.hashCode());
}
}
3.结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
1.适配器模式
概述:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
主要解决:在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的
案例:
//要被适配的类:网线
public class Adaptee {
public void requst(){
System.out.println("连接网线上网");
}
}
//客户端类:电脑,想上网,但是插不上网线
public class Computer {
public void net(NetToUSB adapter){
adapter.handleRequst();
}
}
public interface NetToUSB {
void handleRequst();
}
public class Adapter extends Adaptee implements NetToUSB{
@Override
public void handleRequst() {
super.requst();
}
}
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();//电脑
Adaptee adaptee = new Adaptee();//网线
Adapter adapter = new Adapter();//转接器
Adapter2 adapter2 = new Adapter2(adaptee);//转接器
computer.net(adapter);
}
}
使用的时候发现这个适配器是直接连接到网线的,而且这个适配器只能连接一个网线,因为只能单继承,所以我们要将适配器改进一下,改为组合使用
public class Adapter2 implements NetToUSB{
private Adaptee adaptee;
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void handleRequst() {
adaptee.requst();
}
}
2.桥接模式
概念:将抽象部分与实现部分分离,使它们都可以独立的变化
结构:
- 抽象化(Abstraction) 角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction) 角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor) 角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor) 角色:给出实现化角色接口的具体实现。
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景:一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
原案例:
这里如果要加入一个一个电脑类型就需要在加入三个品牌,这样就多了四个类,会产生类本章的情况,扩展性会出现问题
桥接案例:
将电脑类型与品牌实现分离,这样在扩展的时候会不会互相耦合,也有了良好的扩展性
//品牌
public interface Brand {
void info();
}
//联想品牌
public class Lenovo implements Brand {
@Override
public void info() {
System.out.print("联想");
}
}
//苹果品牌
public class Apple implements Brand {
@Override
public void info() {
System.out.print("苹果");
}
}
//抽象的电脑类
public class Computer {
protected Brand brand;
public Computer(Brand brand) {
this.brand = brand;
}
public void info(){
brand.info();//自带品牌
}
}
class Desktop extends Computer{
public Desktop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.print("台式机");
}
}
class Laptop extends Computer{
public Laptop(Brand brand) {
super(brand);
}
@Override
public void info() {
super.info();
System.out.println("笔记本");
}
}
public class Test {
public static void main(String[] args) {
Computer computer = new Laptop(new Apple());
computer.info();
}
}
再理解:
java虚拟机运行的java程序可以在系统的各个系统下运行,无论增加任何系统或者任何java程序并不会影响对方的程序
3.代理模式
概述:为其他对象提供一个代理以便控制这个对象的访问。
结构:
- 抽象主题(Subject) 类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject) 类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
优点: 1、职责清晰:房东只卖房,中介去管其他的事,客户只能访问中介,对于房东来说,起到了一个保护的作用。 2、高扩展性。 3、客户端与目标对象实现分离,降低了系统耦合性。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:防火墙代理:防火墙将你的浏览器请求发给互联网,将互联网返回响应时,代理服务器将转给浏览器
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
静态代理
代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共角色就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低~
角色分析
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人!
房东只想出租房子,别的事情我不干,租房的其他事情交给中介去代理
//租房
public interface Rent {
void rent();
}
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
//代理角色
public class Proxy implements Rent{
private Host host;
public Proxy(Host host) {
this.host = host;
}
public Proxy() {
}
@Override
public void rent() {
seeHost();
host.rent();
hetong();
fare();
}
//看房
public void seeHost(){
System.out.println("中介带你去看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
//租房
public void hetong(){
System.out.println("签租赁合同");
}
}
public class Client {
public static void main(String[] args) {
//房东
Host host = new Host();
//代理,中介帮房东租房子,但是呢,代理一般会有一些附属操作
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介租房即可
proxy.rent();
}
}
public interface UserService {
void add();
void delete();
void update();
void query();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public UserServiceProxy() {
}
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("使用了"+msg+"方法");
}
}
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
//proxy代理了userService
proxy.setUserService(userService);
proxy.delete();
}
}
动态代理
动态代理的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共角色就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!
动态代理的使用:
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口 — JDK动态代理【我们在这里使用】
- 基于类:cglib
- java字节码实现:javassist
需要了解两个类:Proxy: 代理;InvocationHandler:调用处理程序。
//租房
public interface Rent {
void rent();
}
//房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
//我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成得到代理类
//newProxyInstance生成一个代理对象(getClassLoader(获取类在哪个位置),要代理的接口是哪一个,InvocationHandler)
// 获取当前类的加载器,获取rent接口,获取当前类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现! invoke执行什么方法
Object result = method.invoke(rent, args);
seeHose();
fee();
return result;
}
public void seeHose(){
System.out.println("中介带着看房子!");
}
public void fee(){
System.out.println("中介收取费用!");
}
}
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象!
pih.setRent(host);
Rent proxy = (Rent) pih.getProxy(); //这里的proxy就是动态生成的,我们并没有写
proxy.rent();
}
}
public interface UserService {
void add();
void delete();
void update();
void query();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
//我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {//ProxyInvocationHandler生成动态代理实例 InvocationHandler调用处理代理实例,并返回结果
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String msg){
System.out.println("使用了"+msg+"方法");
}
}
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService);//设置要代理的对象
//动态生成代理类 这里父类的类型必须是接口的类型
UserService proxy = (UserService) pih.getProxy();
proxy.add();
}
}
4.组合模式
概述:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
结构:
- 抽象根节点:定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
- 树枝节点:定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
- 叶子节点:叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
优点: 1、高层模块调用简单。 2、节点*增加。
- 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
- 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
- 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。
主要解决:在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
案例:
//菜单组件,属于抽象根节点
public abstract class MenuComponent {
//菜单组件的名称
protected String name;
//菜单组件的层级
protected int level;
//添加子菜单
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
//移除子菜单
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
//获取指定的子菜单
public MenuComponent getChild(int index){
throw new UnsupportedOperationException();
}
//获取菜单或者菜单项的名称
public String getName(){
return name;
}
//打印菜单名称的方法(包含子菜单和子菜单项)
public abstract void print();
}
//菜单类:属于树枝节点
public class Menu extends MenuComponent {
//菜单可以有多个子菜单或子菜单项
private List<MenuComponent> menuComponentList=new ArrayList<MenuComponent>();
//构造方法
public Menu(String name,int level){
this.name=name;
this.level=level;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
@Override
public void print() {
//打印菜单名称
for (int i=0;i<level;i++){
System.out.print("--");
}
System.out.println(name);
//打印子菜单或者子菜单项名称
for (MenuComponent component : menuComponentList) {
component.print();
}
}
}
//菜单项:叶子节点
public class MenuItem extends MenuComponent{
public MenuItem(String name,int level){
this.name=name;
this.level=level;
}
@Override
public void print() {
//打印菜单项的名称
for (int i=0;i<level;i++){
System.out.print("--");
}
System.out.println(name);
}
}
public class Client {
public static void main(String[] args) {
//创建菜单树
MenuComponent menu1= new Menu("菜单管理",2);
menu1.add(new MenuItem("页面访问",3));
menu1.add(new MenuItem("展开菜单",3));
menu1.add(new MenuItem("编辑菜单",3));
menu1.add(new MenuItem("删除菜单",3));
menu1.add(new MenuItem("新增菜单",3));
MenuComponent menu2= new Menu("权限管理",2);
menu2.add(new MenuItem("页面访问",3));
menu2.add(new MenuItem("提交保存",3));
MenuComponent menu3= new Menu("角色管理",2);
menu3.add(new MenuItem("页面访问",3));
menu3.add(new MenuItem("新增角色",3));
menu3.add(new MenuItem("修改角色",3));
//创建一级菜单
MenuComponent component=new Menu("系统菜单",1);
//将二级菜单添加到一级菜单中
component.add(menu1);
component.add(menu2);
component.add(menu3);
///打印菜单名称(如果有子菜单一块打印)
component.print();
}
}
5.装饰模式
概述:动态地给一个对象添加一些新的功能。就增加功能来说,装饰器模式相比生成子类更为灵活。
结构:
- 装饰(Decorator) 模式中的角色
- 抽象构件(Component) 角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component) 角色:实现抽象构件,通过装饰角色为其添加一 些职责。
- 抽象装饰(Decorator) 角色:继承或实现抽象构件, 并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator) 角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。完美遵循开闭原则
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
原版例:
一个快餐店的例子,快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、 培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。
这时候如果要加入一个火腿肠的配菜,要在两个主菜下都加入火腿肠的实现,如果要加入一个盖浇面的主菜,就需要顺便再加入火腿肠、鸡蛋、培根三个子类,就会出现子类膨胀的情况
装饰者模式改进案例:
将炒面和炒饭继承快餐类,并给定描述和价格,在后续添加主食,只需在添加一个类就行,不需要在去管理哪些配菜。
右边加入了一个装饰者类来聚合了快餐类来管理配菜的名称加主食的价格,如果要加入配菜,只需继承装饰者类即可,避免了子类膨胀的情况,并与主食避免了相互耦合的情况
//快餐类:抽象构建角色
public abstract class FastFood {
private float price;//价格
private String desc;//描述
//getset、有参无参
//我们只有知道了具体的快餐之后才能计算出快餐的价格
public abstract float cost();
}
//炒饭:具体构建角色
public class FriedRice extends FastFood{
public FriedRice() {
super(10,"炒饭");
}
@Override
public float cost() {
return getPrice();
}
}
//炒面:具体构建角色
public class FriedNoodles extends FastFood{
public FriedNoodles() {
super(12,"炒面");
}
@Override
public float cost() {
return getPrice();
}
}
//装饰者类:抽象装饰者角色
public abstract class Garnish extends FastFood {
//声明快餐类的变量
private FastFood fastFood;
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(FastFood fastFood, float price, String desc) {
super(price, desc);
this.fastFood = fastFood;
}
}
//鸡蛋类:具体的装饰者角色
public class Egg extends Garnish{
public Egg(FastFood fastFood){
super(fastFood,1,"鸡蛋");
}
@Override
public float cost() {
//计算价格
return getPrice() +getFastFood().cost();
}
@Override
public String getDesc() {
return super.getDesc()+getFastFood().getDesc();
}
}
//培根类:具体的装饰者角色
public class Bacon extends Garnish{
public Bacon(FastFood fastFood){
super(fastFood,2,"培根");
}
@Override
public float cost() {
//计算价格
return getPrice() +getFastFood().cost();
}
@Override
public String getDesc() {
return super.getDesc()+getFastFood().getDesc();
}
}
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood friedRice= new FriedRice();
System.out.println(friedRice.getDesc()+" "+friedRice.cost()+"元");
System.out.println("========");
//在上面的炒饭中加一个鸡蛋
friedRice=new Egg(friedRice);
System.out.println(friedRice.getDesc()+" "+friedRice.cost()+"元");
//在上面的炒饭中再加一个鸡蛋
friedRice=new Egg(friedRice);
System.out.println(friedRice.getDesc()+" "+friedRice.cost()+"元");
System.out.println("========");
//在上面的炒饭中再加一个培根
friedRice=new Bacon(friedRice);
System.out.println(friedRice.getDesc()+" "+friedRice.cost()+"元");
}
}
6.享元模式
概念:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
结构:
- 状态:
- 内部状态,即不会随着环境的改变而改变的可共享部分。
- 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
- 角色:
- 抽象享元角色(Flyweight) : 通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- 具体享元(Concrete Flyweight) 角色:它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个 具体享元类提供唯一-的享元对象。
- 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
- 享元工厂(Flyweight Factory) 角色:负责创建和管理享元角色。当客户对象请求一个享元对象时, 享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
优点:
- 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
- 享元模式中的外部状态相对独立,且不影响内部状态
缺点:
- 为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
使用场景:1、一个系统有大量相同或者相似的对象,造成内存的大量耗费。
2、在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源, 因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
主要解决:在有大量对象时,有可能会造成内存溢出,我们把其*同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
案例:俄罗斯方块
下面的图片是众所周知的俄罗斯方块中的一一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象, 这些对象就要占用很多的内存空间,下面利用享元模式进行实现。
//抽象享元角色
public abstract class AbstractBox {
//获取图形的方法
public abstract String getShape();
//显示图形及颜色
public void display(String color){
System.out.println("方块形状:"+getShape()+",颜色:"+color);
}
}
//I图形类:具体享元角色
public class Ibox extends AbstractBox {
@Override
public String getShape() {
return "I";
}
}
//L图形类:具体享元角色
public class Lbox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
//O图形类:具体享元角色
public class Obox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
//将该类设计为单例:享元工厂类
public class BoxFactory {
private HashMap<String,AbstractBox> map;
//在构造方法中进行初始化操作
private BoxFactory(){
map=new HashMap<String,AbstractBox>();
map.put("I",new Ibox());
map.put("O",new Obox());
map.put("L",new Lbox());
}
//饿汉式单例
private static BoxFactory factory=new BoxFactory();
//提供一个方法获取该工厂类对象
public static BoxFactory getInstance(){
return factory;
}
//根据名称获取图形对象
public AbstractBox getShape(String name){
return map.get(name);
}
}
public class Client {
public static void main(String[] args) {
//获取I图形对象
AbstractBox boxI = BoxFactory.getInstance().getShape("I");
boxI.display("灰色");
//获取L图形对象
AbstractBox boxL = BoxFactory.getInstance().getShape("L");
boxL.display("绿色");
//获取O图形对象
AbstractBox boxO = BoxFactory.getInstance().getShape("O");
boxO.display("红色");
//获取O图形对象
AbstractBox boxO2 = BoxFactory.getInstance().getShape("O");
boxO.display("灰色");
System.out.println("两次获取到的O图形是否是同一个对象:"+(boxO==boxO2));
}
}
7.外观模式
概念:又名门面模式,是一 种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口, 外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
结构:
- 外观(Facade) 角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System) 角色:实现系统的部分功能,客户可以通过外观角色访问它。
好处:
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
缺点:
- 不符合开闭原则,修改很麻烦
使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。
主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。
案例:智能家电控制
小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调; 操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:
//电灯类:子系统角色
public class Light {
public void on(){
System.out.println("打开电灯");
}
public void off(){
System.out.println("关闭电灯");
}
}
//电视机类:子系统角色
public class TV {
public void on(){
System.out.println("打开电视机");
}
public void off(){
System.out.println("关闭电视机");
}
}
//空调类:子系统角色
public class AirCondition {
public void on(){
System.out.println("打开空调");
}
public void off(){
System.out.println("关闭空调");
}
}
//只能家居控制音响:外观类
public class SmartAppliancesFacade {
//聚合电灯对象、电视机对象、空调对象
private Light light;
private TV tv;
private AirCondition airCondition;
public SmartAppliancesFacade() {
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}
public void say(String massage) {
if (massage.contains("打开家电")) {
on();
} else if (massage.contains("关闭家电")) {
off();
} else {
System.out.println("我听不懂你在讲什么东西");
}
}
//一键打开功能
private void on() {
light.on();
tv.on();
airCondition.on();
}
//一键关闭功能
private void off() {
light.off();
tv.off();
airCondition.off();
}
}
public class Client {
public static void main(String[] args) {
//创建智能音响对象
SmartAppliancesFacade facade = new SmartAppliancesFacade();
//控制家电
facade.say("打开家电");
System.out.println("===========");
facade.say("关闭家电");
}
}