目录
1、简单工厂(严格来说简单工厂不算设计模式,因为不符合开闭原则)
设计模式介绍
核心概念:设计模式是什么?设计模式是思想,是一种设计思想。只要核心思想不变,同一种思想可以有多种代码实现方案,并非是死板的某一种形式,它没有固定的实现代码
1、设计模式的六大原则
总原则:开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
(1)单一职责原则
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
(2)里氏替换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
(3)依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
(4)接口隔离原则(Interface Segregation Principle)
这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
(5)迪米特法则(最少知道原则)(Demeter Principle)
就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
(6)合成复用原则(Composite Reuse Principle)
原则是尽量首先使用合成/聚合的方式,而不是使用继承。
2、设计模式分类
1、创建型模式(在创建对象的过程中尽量隐藏创建细节,不直接使用new)
- 单例模式(Singleton Pattern)
- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
2、结构型模式(主要关注类和对象的继承、组合)
- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter、Criteria Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
3、行为型模式(关注对象之间的通信)
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 空对象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
一、单例模式【创建型模式】
这一模式的目的是使得类的一个对象成为系统中的唯一实例
优点:
- 在内存中只有一个实例,减少内存开销
- 避免资源多重占用
- 设置全局访问点,严格控制访问
JAVA中单例模式有两种构建方式
- 饿汉方式(立即加载):指全局的单例实例在类装载时构建
- 懒汉方式(延迟加载):指全局的单例实例在第一次被使用时构建
注意要点:私有构造,这样才能避免该类被外部程序再次创建(被new),从而达到单例
实现方式
1、饿汉方式(立即加载)
// 饿汉式单例
public class Singleton1 {
// 私有构造
private Singleton1() {}
private final static Singleton1 single = new Singleton1();
// 静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
2、懒汉方式(延迟加载)
// 懒汉式单例
public class Singleton2 {
// 私有构造
private Singleton2() {}
private static Singleton2 single = null;
public static Singleton2 getInstance() {
if(single == null){
single = new Singleton2();
}
return single;
}
}
该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个single对象,如何改造请看以下方式:
使用synchronized同步锁
public class Singleton3 {
// 私有构造
private Singleton3() {}
private static Singleton3 single = null;
public static Singleton3 getInstance() {
// 等同于 synchronized public static Singleton3 getInstance()
synchronized(Singleton3.class){
// 注意:里面的判断是一定要加的,否则出现线程安全问题
if(single == null){
single = new Singleton3();
}
}
return single;
}
}
在方法上加synchronized同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
二、工厂方法模式【创建型模式】
优点:
- 用户只需关心产品对应的工厂,无需关心创建细节
- 加入新产品符合开闭原则,提高了扩展性
缺点:
- 类的个数容易过多,增加复杂度
- 增加了系统的抽象性和理解难度
应用场景
- 创建需要大量重复的代码
- 应该用层不需要依赖于产品类如何被创建、实现等细节
工厂模式的分类及特点
- 简单工厂:需要修改原有代码
- 工厂方法模式:改善简单工厂的缺点,不会修改原有代码,只需新增
- 抽象工厂模式:
1、简单工厂(严格来说简单工厂不算设计模式,因为不符合开闭原则)
只有一个工厂,每增加一个新需求,就需要修改这个工厂并在其中添加新的代码(判断)。重点是它需要修改原有代码。
所谓的简单工厂,不用管是什么产品,我们将所有产品的生产线一股脑的都放在一个工厂里,这样工厂便可以根据用户的要求来生产各种产品。
定义一些被生产的基本类
public abstract class Phone{
public abstract void call();
}
class HuaWei extends Phone{
public void call() {
System.out.println("hua wei");
}
}
class XiaoMi extends Phone{
public void call() {
System.out.println("xiao mi");
}
}
根据给工厂传入值的不同成产出不同的对象
//工厂类
public class Factory{
public static Phone creator(String which){
if ("huawei".equalsIgnoreCase(which))
return new HuaWei();
if ("xiaomi".equalsIgnoreCase(which))
return new XiaoMi();
retrun null;
}
}
//测试类
public class DTest {
public static void main(String[] args) {
Phone phone=Factory.creator("huawei");
if(null==phone){return;}
phone.call();
}
}
2、工厂方法模式
定义一个创建对象的接口,让实现这个接口的类决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行
- 抽象工厂不负责生产,生产的工作放在具体的子类工厂里面
- 一旦有新的需求就添加对应的子类工厂,而不用改变任何原有代码,这样的方式使得原来的产品生产线不受新产品的影响实现了解耦,符合了设计模式中的开闭原则(对扩展开放,对修改关闭)
1、定义一些被生产的基本类
//产品接口
public interface Phone {
public void call();
}
//具体产品
public class HuaWei implements Phone{
public void call() {
System.out.println("hua wei");
}
}
public class XiaoMi implements Phone{
public void call() {
System.out.println("xiao mi");
}
}
2、定义总工厂(不上产具体对象)和一些对应的子工厂 (生产对象)
//总工厂(工厂抽象类),不生产具体对象
public abstract class PhoneFactory {
public abstract Phone getPhone();
}
//子工厂,负责生产对象
class HuaWeiFactory extends PhoneFactory{
public Phone getPhone() {
return new HuaWei();
}
}
class XiaoMiFactory extends PhoneFactory{
public Phone getPhone() {
return new XiaoMi();
}
}
//随时有新的需求,我们可以建新的子工厂
......
3、测试类
随时有新的需求,我们可以创建新的工厂继续继承总工厂为父类。不用改变其他原有代码,只是进行添加。
(这就是所说的开闭原则,可扩展,不修改)
public class DTest{
public static void main(String[] args) {
PhoneFactory phoneFactory=new HuaWeiFactory();
Phone phone=phoneFactory.getPhone();
if(null==phone){return;}
phone.call();
}
}
三、抽象工厂模式【创建型模式】
抽象工厂模式和工厂模式的区别:
1、这是一个场景应用复杂度的区别
2、这两种设计模式主要的区别在于产品,
工厂模式是用来创建同一个产品的不同类型
(e.g:早餐店里卖便宜或贵的包子)
但是抽象工厂模式是用来创建不同类的产品
(e.g:早餐店里有便宜的档口卖便宜的包子和便宜的粥;还有贵的档口卖贵的包子和贵的粥;还有其他档口的组合方式)
3、一个工厂可以产生多个产品,而每个产品又可以被多个工厂生产
4、一般来说,产品种类单一(一对多),适合用工厂模式;
如果有多个种类,各种类型时(多对多),通过抽象工厂模式来进行创建是很合适的。
为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定他们的具体实现类。
例如:
Q3和Q7有不同的轮胎、发动机、制动系统。虽然生产的零件不同,型号不同。但是根本上都有共同的约束,就是轮胎、发动机、制动系统。设计如下:
- 需要一个抽象工厂,里面有三个接口分别为生产轮胎、发动机、制动系统,抽象类
- 需要三个抽象产品分别为轮胎、发动机、制动系统,抽象接口
- 需要实现上面的三个抽象接口,定义出每个接口不通的对象,比如:普通轮胎和越野轮胎
- 需要两个具体类继承自上面的抽象类,实现具体的工厂,比如:生产Q3的工厂和生产Q7的工厂
- 在客户端new出对应的具体工厂并调用对应的生产方法
区别
工厂方法模式: 只有一个抽象产品类,具体工厂类只能创建一个具体产品类的实例
抽象工厂模式: 有多个抽象产品类 ,具体工厂类能创建多个具体产品类的实例
1、创建零件组
public interface IBrake {
/**
*制动系统
*/
void brake();
}
public class NormalBrake implements IBrake {
public void brake() {
System.out.println("普通制动");
}
}
public class SeniorBrake implements IBrake {
public void brake() {
System.out.println("高级制动");
}
}
//---------------------------------------------------
public interface IEngine {
/**
*发动机
*/
void engine();
}
public class NormalEngine implements IEngine {
public void engine() {
System.out.println("普通发动机");
}
}
public class SeniorEngine implements IEngine {
public void engine() {
System.out.println("高级发动机");
}
}
//---------------------------------------------------
public interface ITire {
/**
* 轮胎
*/
void tire();
}
public class NormalTire implements ITire {
public void tire() {
System.out.println("普通轮胎");
}
}
public class SeniorTire implements ITire {
@Override
public void tire() {
System.out.println("高级轮胎");
}
}
2、创建抽象工厂(重点在于此)
public abstract class CarFactory {
/**
* 生产轮胎
* @return 轮胎
* */
public abstract ITire createTire();
/**
* 生产发动机
* @return 发动机
* */
public abstract IEngine createEngine();
/**
* 生产制动系统
* @return 制动系统
* */
public abstract IBrake createBrake();
}
3、创建你对应型号汽车的组装工厂(继承抽象工厂,实现对应的方法)
- 用上面的零件组装出你这个工厂设定的汽车配置
- 一个工厂只组装一个型号的汽车,如果要别的型号,再建一个工厂进行生产
public class Q3Factory extends CarFactory {
@Override
public ITire createTire() {
// TODO Auto-generated method stub
return new NormalTire();
}
@Override
public IEngine createEngine() {
// TODO Auto-generated method stub
return new SeniorEngine();
}
@Override
public IBrake createBrake() {
// TODO Auto-generated method stub
return new NormalBrake();
}
}
4、测试
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
CarFactory q3=new Q3Factory();
q3.createBrake().brake();
q3.createEngine().engine();
q3.createTire().tire();
}
}
疑问:为什么叫抽象工厂模式?仅仅是因为里面是抽象类还是说这是抽象的概念?那可以将抽象类换成接口吗?
个人理解: 抽象工厂模式大多是为了处理复杂度较高的多个产品实例,但是在这种情况下是否会出现与个别没有那么复杂的实例共存情况?比如汽车轮胎有很多种,但是发动机只有一种。那么我们能不能直接在抽象类中使用普通方法直接取到这个发动机对象呢?这样其他的产品还是用抽象方法获取,这样就减少了个别对象的复杂度。这种情况下接口就必须也单独实现每个方法,会增加代码量(这是我自己的猜想,未经证实,所以大家有正确答案请留言回复我,我将更新这一问题)
四、原型模式【创建型模式】
使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象
也就是说,创建一个类似于模板的原型,一直存储不动,一旦需要新的对象,我们就克隆这个原型,我们操作修改都是修改这个被克隆出来的对象,而不影响我们的原型模板。
举个栗子:我们可以思考一下动漫中的影分身。
原型模式分三个角色:抽象原型类,具体原型类,客户类
抽象原型类(prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口,抽象类甚至是一个具体的实现类。
具体原型类(concretePrototype):它实现了抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象。
客户类(Client):在客户类中,使用原型对象只需要通过工厂方式创建或者直接NEW(实例化一个)原型对象,然后通过原型对象的克隆方法就能获得多个相同的对象。由于客户端是针对抽象原型对象编程的所以还可以可以很方便的换成不同类型的原型对象!
浅克隆于深克隆:在原型模式中有两个概念我们需要了解一下,就是浅克隆和深克隆的概念。按照我的理解,浅克隆只是复制了基础属性,列如八大基本类型,然而引用类型实际上没有复制,只是将对应的引用给复制了!复制地址。
1、浅克隆
在浅克隆中,如果原型对象的成员变量是值类型(八大基本类型,byte,short,int,long,char,double,float,boolean).那么就直接复制,如果是复杂的类型,(枚举,String,对象)就只复制对应的内存地址。
在换个说法,就是复杂类型的成员变量(String,枚举,啥的)用的是一个。修改了克隆对象的原型对象也会变。他们是共用的。而值类型不是共用的!
2、深克隆
深克隆,我就不用多说了吧,就是什么都是单独的!全部复制,然后各自独立。你修改克隆对象对于原型对象没有丝毫影响,完全的影分身!
五、适配器模式【结构型模式】
核心思想:将一个类的接口转换成客户希望的另一个接口。adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
适配器模式的定义:将某个类的接口转换为接口客户所需的类型。换句话说,适配器模式解决的问题是,使得原本由于接口不兼容而不能一起工作、不能统一管理的那些类可以在一起工作、可以进行统一管理。主要作用就是兼容
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式
现实世界到代码的转换 电源插座代码示例
1、对象适配器
此适配器 实现了目标接口,并且拥有一个Adaptee对象 作为属性
港版插座面板
package adapter_pattern;
/**
* 目标角色 Target 接口
* 香港地区使用的插座面板,提供输出电流的功能
*/
public interface HongKongPanel {
void offerHongKongElectricity();
}
package adapter_pattern;
/**
* 目标角色 Target 某个具体的港版插座面板 实现类
* 香港地区使用的插座面板,提供输出电流的功能
*/
public class HongKongPanelimpl implements HongKongPanel {
@Override
public void offerHongKongElectricity() {
System.out.println("港版面板 提供电流");
}
}
大陆地区插座面板
package adapter_pattern;
/**
* 被适配角色 Adaptee 接口
* 大陆地区使用的插座面板,提供输出电流的功能
*/
public interface ChinaMainlandPanel {
void offerChinaMainlandElectricity();
}
package adapter_pattern;
/**
* 被适配角色 Adaptee 某种具体类型的插座面板 实现类
* 大陆地区使用的插座面板,提供输出电流的功能
*/
public class ChinaMainlandPanelimpl implements ChinaMainlandPanel {
@Override
public void offerChinaMainlandElectricity() {
System.out.println("国标面板 提供电流");
}
}
港版插头
package adapter_pattern;
/**
* 客户角色 Client 港版插头
*/
public class ClientHongKongSocket {
/**
* 接受港版插座面板作为参数
* 港版插头,插入到港版插座面板
*
* @param hongKongPanel
*/
public void plugIn(HongKongPanel hongKongPanel) {
System.out.println("港版插头 插入");
hongKongPanel.offerHongKongElectricity();
}
}
插头适配器
package adapter_pattern;
/**
* 适配器角色 Adapter
* 实现目标角色 TargetHongkongPanelInterface
* 组合使用被适配角色 AdapteeChinaMainlandPanelInterface
* 将对目标角色的方法调用转换为被适配角色的方法调用
*/
public class Adapter implements HongKongPanel {
private ChinaMainlandPanel chinaMainlandPanel;
public Adapter(ChinaMainlandPanel chinaMainlandPanel) {
this.chinaMainlandPanel = chinaMainlandPanel;
}
/**
* 适配器重写的这个方法,目的是进行转换
*/
@Override
public void offerHongKongElectricity() {
System.out.println("适配器开始转换功能");
chinaMainlandPanel.offerChinaMainlandElectricity();
}
}
使用方法
package adapter_pattern;
/**
* 适配器模式
* 核心思想【对象功能的兼容】
* 将一个类的接口转换成客户希望的另一个接口。adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
*/
public class Demo {
/**
* 测试主程序,港版插头 插入到适配器上
* 适配器插入到大陆面板上
*/
public static void main(String[] args) {
//港版插头
ClientHongKongSocket socket = new ClientHongKongSocket();
System.out.println("\n======================【非适配器模式】港版插头 只能插入 港版面板======================");
//港版面板
HongKongPanel hongKongPanel = new HongKongPanelimpl();
//港版插头 原先只能插到 港版面板
socket.plugIn(hongKongPanel);
System.out.println("\n======================【适配器模式】港版插头 可以插入 国际面板======================");
//大陆面板
ChinaMainlandPanel chinaMainlandPanel = new ChinaMainlandPanelimpl();
//适配器(内部做了转换)
Adapter adapter = new Adapter(chinaMainlandPanel);
//港版插头 插到 适配器上
socket.plugIn(adapter);
}
}
测试结果输出日志
======================【非适配器模式】港版插头 只能插入 港版面板======================
港版插头 插入
港版面板 提供电流
======================【适配器模式】港版插头 可以插入 国际面板======================
港版插头 插入
适配器开始转换功能
国标面板 提供电流
适配器 实现了目标接口,并且拥有一个Adaptee对象 作为属性,很显然就是对象适配器
此案例内容来自:设计模式之适配器模式 adapter 适配器模式分类概念角色详解 类适配器 对象适配器 接口适配器 双向适配器 - 云+社区 - 腾讯云
代理模式【结构型模式】
核心思想:代理模式强调“代理执行",主要目的在控制对象的访问上。被代理对象不对外暴露,不可以被外部直接访问或者操作【虽然代理模式可能会包含功能增强,但是很明显,这里的man不再是之前那个man而且"你也不能直接操作man"】
通常会在⼀个代理类中创建⼀个被代理类的对象而非外部传入。当然你说你非要在外部 new 一个被代理对象再通过构造或其他方式传进来,咱也不是说不可以是吧!!!还是那句话,设计模式是思想。
装饰器模式【结构型模式】
核心思想:主要目的在于增加对象的行为,关注于在⼀个对象上动态的添加⽅法。装饰者模式强调的是在不修改原本对象的前提下,使原本的对象"功能增强"
这就是和代理模式的代码不同之处,代理模式一定是自身持有这个对象,不需要从外部传入。而装饰模式的一定是从外部传入,并且可以没有顺序,按照代码的实际需求随意挑换顺序,就如你吃火锅先放白菜还是先放丸子都可以
代理模式和装饰器模式的区别
- 装饰器模式通常的做法是将原始对象作为⼀个参数传给装饰者的构造器【用户是允许直接操作原始对象】;⽽代理模式通常在⼀个代理类中创建⼀个被代理类的对象【被代理对象是不可以被用户直接访问或者操作】
- 装饰器模式关注于在⼀个对象上动态的添加⽅法;然而代理模式关注于控制对对象的访问。(两者都是结构型设计模式)
- 1、代理模式强调"代理执行"(虽然可能会包含功能增强,但是很明显,这里的man不再是之前那个man而且"你也不能直接操作man")
2、装饰者模式强调的是在不修改原本对象的前提下,使原本的对象"功能增强"
装饰器模式 | 代理模式 |
---|---|
主要目的在于增加对象的行为,关注于在⼀个对象上动态的添加⽅法 | 主要目的在控制对象的访问,关注于控制对对象的访问 |
通常的做法是将原始对象作为⼀个参数传给装饰者的构造器【用户是允许直接操作原始对象】 | 通常在⼀个代理类中创建⼀个被代理类的对象【被代理对象是不可以被用户直接访问或者操作】 |
装饰者模式强调的是在不修改原本对象的前提下,使原本的对象"功能增强" | 代理模式强调"代理执行"(虽然可能会包含功能增强,但是很明显,这里的man不再是之前那个man而且"你也不能直接操作man") |
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
类结构如下
1、定义一个图形绘画接口
package decorator_pattern;
/**
* 图形绘画接口
*/
public interface Shape {
void draw();
}
2、创建两个真正绘画图形的实现类,来实现绘画方法(圆形、矩形)
package decorator_pattern;
/**
* 圆形
*/
public class Circle implements Shape {
@Override
public void draw() {
System.out.println(String.format("Circle类-》draw()圆形绘图开始【对象信息:%s】", this));
}
}
package decorator_pattern;
/**
* 矩形
*/
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println(String.format("Rectangle类-》draw()矩形绘图开始【对象信息:%s】", this));
}
}
3、创建一个抽象类装饰器【基类】,同样实现了Shape接口
重点在于构造器这里是使用的是传入的参数
这里的draw()绘画方法可实现也可不实现
不实现会由后面继承的类进行实现
实现了会由后面继承的类重写
package decorator_pattern.decorator;
import decorator_pattern.Shape;
/**
* 图形装饰器抽象类【基类】
*/
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
/**
* 这个方法有可能会被子类重写覆盖
*/
@Override
public void draw() {
System.out.println(String.format("图形装饰器抽象类【基类】:draw()【对象信息:%s】", decoratedShape));
decoratedShape.draw();
}
}
4、这里创建了两个真正的装饰器类,继承抽象类装饰器【基类】,重写draw()方法
- 重点在于构造器这里是使用的是传入的参数
- 重写draw()方法,里面使用的是构造传入的对象,然后又调用了自己的独有装饰方法
package decorator_pattern.decorator;
import decorator_pattern.Shape;
/**
* 红色边框装饰器
*/
public class RedBorderDecorator extends ShapeDecorator {
public RedBorderDecorator(Shape decoratedShape) {
super(decoratedShape);
}
/**
* 重写绘画方法
*/
@Override
public void draw() {
decoratedShape.draw();
System.out.println(String.format("RedShapeDecorator类-》draw()【对象信息:%s】", decoratedShape));
setRedBorder(decoratedShape);
}
/**
* 自己私有的装饰方法:加红色边框
* @param decoratedShape
*/
private void setRedBorder(Shape decoratedShape) {
System.out.println(String.format("RedShapeDecorator类-》setRedBorder()设置边框颜色:Border Color: Red【对象信息:%s】", decoratedShape));
}
}
package decorator_pattern.decorator;
import decorator_pattern.Shape;
/**
* 蓝色背景装饰器
*/
public class BlueBackdropDecorator extends ShapeDecorator {
public BlueBackdropDecorator(Shape decoratedShape) {
super(decoratedShape);
}
/**
* 重写绘画方法
*/
@Override
public void draw() {
decoratedShape.draw();
System.out.println(String.format("BlueBackdropDecorator类-》draw()【对象信息:%s】", decoratedShape));
setBlueBackdrop(decoratedShape);
}
/**
* 自己私有的装饰方法:加蓝色背景
*
* @param decoratedShape
*/
private void setBlueBackdrop(Shape decoratedShape) {
System.out.println(String.format("BlueBackdropDecorator类-》setBlueBackdrop()设置背景颜色:Backdrop Color: Blue【对象信息:%s】", decoratedShape));
}
}
5、使用方法
package decorator_pattern;
import decorator_pattern.decorator.BlueBackdropDecorator;
import decorator_pattern.decorator.RedBorderDecorator;
import decorator_pattern.decorator.ShapeDecorator;
/**
* 装饰器模式
* 核心思想【对象功能的增强】
* 主要目的在于增加对象的行为,关注于在一个对象上动态的添加方法。装饰者模式强调的是在不修改原本对象的前提下,使原本的对象"功能增强"
*/
public class Demo {
public static void main(String[] args) {
System.out.println("==================画圆形【直调,非装饰器模式】==================");
Shape circleShape = new Circle();
circleShape.draw();
System.out.println("\n==================画圆形:装饰了红色边框【装饰器模式】==================");
Circle circle = new Circle();
System.out.println(String.format("main方法创建 new Circle() 圆形对象【对象信息:%s】", circle));
ShapeDecorator circleDecorator = new RedBorderDecorator(circle);
circleDecorator.draw();
System.out.println("\n==================画矩形:装饰了红色边框 and 蓝色背景【装饰器模式】==================");
Rectangle rectangle = new Rectangle();
System.out.println(String.format("main方法创建 new Rectangle() 矩形对象【对象信息:%s】", rectangle));
ShapeDecorator rectangleDecorator = new RedBorderDecorator(new BlueBackdropDecorator(rectangle));
rectangleDecorator.draw();
}
}
测试结果输出日志:
通过日志可以看出装饰器模式使用的对象都是同一个
这就是装饰器模式和代理模式的最大区别:
- 装饰器模式使用的是对象自己本身,并对自己进行扩展
- 代理模式使用的是代理对象,而非原对象
==================画圆形【直调,非装饰器模式】==================
Circle类-》draw()圆形绘图开始【对象信息:decorator_pattern.Circle@4b67cf4d】
==================画圆形:装饰了红色边框【装饰器模式】==================
main方法创建 new Circle() 圆形对象【对象信息:decorator_pattern.Circle@7ea987ac】
Circle类-》draw()圆形绘图开始【对象信息:decorator_pattern.Circle@7ea987ac】
RedShapeDecorator类-》draw()【对象信息:decorator_pattern.Circle@7ea987ac】
RedShapeDecorator类-》setRedBorder()设置边框颜色:Border Color: Red【对象信息:decorator_pattern.Circle@7ea987ac】
==================画矩形:装饰了红色边框 and 蓝色背景【装饰器模式】==================
main方法创建 new Rectangle() 矩形对象【对象信息:decorator_pattern.Rectangle@12a3a380】
Rectangle类-》draw()矩形绘图开始【对象信息:decorator_pattern.Rectangle@12a3a380】
BlueBackdropDecorator类-》draw()【对象信息:decorator_pattern.Rectangle@12a3a380】
BlueBackdropDecorator类-》setBlueBackdrop()设置背景颜色:Backdrop Color: Blue【对象信息:decorator_pattern.Rectangle@12a3a380】
RedShapeDecorator类-》draw()【对象信息:decorator_pattern.decorator.BlueBackdropDecorator@29453f44】
RedShapeDecorator类-》setRedBorder()设置边框颜色:Border Color: Red【对象信息:decorator_pattern.decorator.BlueBackdropDecorator@29453f44】
观察者模式【行为型模式】
责任链模式【行为型模式】
责任链模式 (Chain of Responsibility Pattern) 是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护—个下—节点对象。
当—个清求从链式的首端发出时,会沿看链的路径依次传递给每—个节点对象,直至有对象处理这个清求为止。属于行为型模式。
- Client(客户端):实例化一个处理器的链,在第一个链对象中调用handleRequest 方法。
- Handle(处理器):抽象类,提供给实际处理器继承然后实现handleRequst方法,处理请求
- ConcreteHandler(具体处理器):继承了handler的类,同时实现handleRequst方法,负责处理业务逻辑类,不同业务模块有不同的ConcreteHandler。
代码实现
假设现在去一家公司面试,第一次去一面,第二次去二面,第三次去直接过了。那这个模拟面试代码怎么写呢?
1、首先我们还是定义一个抽象Handler处理器,同时添加一个抽象处理方法 handleRequest,后面我只需要编写具体的处理器来继承Handler类
/**
* 抽象处理者角色
*/
public abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
//处理请求的方法
public abstract void handleRequest(Integer times);
}
2、其次构建第一次面试Handler,内部实现handleRequest方法,判断一下是否是当前处理应该处理的业务逻辑,不是则向下传递。
/**
* 具体处理者角色1:首次面试
*/
public class FirstInterview extends Handler {
@Override
public void handleRequest(Integer times) {
// 条件判断是否是属于当前Handler的处理范围之内,不是则向下传递Handler处理器
if (times == 1) {
// 假设这里是处理的业务逻辑代码
System.out.println("第一次面试" + times);
} else {
nextHandler.handleRequest(times);
}
}
}
3、同样的第二次的SecondInterview和FirstInterview代码基本是一致的,我就不给大家贴出来了,直接看最后一个
/**
* 具体处理者角色3:第三次面试
*/
public class ThreeInterview extends Handler {
@Override
public void handleRequest(Integer times) {
if (times == 3 ) {
System.out.println("第三次面试" + times + ",恭喜面试通过,HR会跟你联系!!!");
} else {
System.out.println("没有人处理该请求!");
}
}
public static void main(String[] args) {
//组装责任链
Handler first = new FirstInterview();
Handler second = new SecondInterview();
Handler three = new ThreeInterview();
first.setNextHandler(second);
second.setNextHandler(three);
//提交请求
// 第一次面试
first.handleRequest(1);
System.out.println();
// 第二次面试
first.handleRequest(2);
System.out.println();
// 第三次面试
first.handleRequest(3);
System.out.println();
}
}
这个结果可以很明显的看出,根据我们传参,不同的Handler根据自己的职责处理着自己的业务,这就是责任链。
责任链模式的优缺点
优点:
- 将请求与处理解耦;
- 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象;
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;
- 链路结构灵活,可以通过改变链路结构动态地新增或删减责任;
- 易于扩展新的请求处理类(节点),符合开闭原则。
缺点:
- 责任链太长或者处理时间过长,会影响整体性能
- 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃;
策略模式【行为型模式】
策略模式( Strategy Pattern )又叫也叫政策模式( Policy Pattern) ,它是将定义的算法家族、分别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。属于行为型模式。
策略模式可以解决在有多种算法相似的情况下,使用if…else 或swith…case所带来的复杂性和臃肿性。
应用场景:
策略模式在生活场景中应用也非常多。
比如一个人的交税比率与他的工资有关,不同的工资水平对应不同的税率。再比如我们在互联网移动支付的大背景下,每次下单后付款前,需要选择支付方式。
在日常业务开发中,策略模式适用于以下场景:
1、针对同一类型问题,有多种处理方式,每一种都能独立解决问题;
2、算法需要自由切换的场景;
3、需要屏蔽算法规则的场景。
从类图中我们可以看到, 有三个参与角色:
- 上下文角色(Context):用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略,算法的直接访问,封装可能存在的变化;
- 抽象策略角色(Strategy):规定策略或算法的行为;
- 具体策略角色(ConcreteStrategy):具体的策略或算法实现;
策略模式代码实例
优惠策略会有很多种可能如 : 领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟,
1、首先我们创建—个促销策略的抽象
PromotionStrategy :
/**
* 促销策略抽象
*/
public interface IPromotionStrategy {
void doPromotion();
}
2、然后分别创建真正的策略执行类
优惠券抵扣策略 CouponStrategy 类
public class CouponStrategy implements IPromotionStrategy {
public void doPromotion() {
System.out.println("使用优惠券抵扣");
}
}
返现促销策略 CashbackStrategy 类
public class CashbackStrategy implements IPromotionStrategy {
public void doPromotion() {
System.out.println("返现,直接打款到支付宝账号");
}
}
拼团优惠策略 GroupbuyStrategy 类
public class GroupbuyStrategy implements IPromotionStrategy {
public void doPromotion() {
System.out.println("5人成团,可以优惠");
}
}
无优惠策略 EmptyStrategy 类
public class EmptyStrategy implements IPromotionStrategy {
public void doPromotion() {
System.out.println("无优惠");
}
}
3、然后创建促销活动方案 PromotionActivity 类 :
public class PromotionActivity {
private IPromotionStrategy strategy;
public PromotionActivity(IPromotionStrategy strategy) {
this.strategy = strategy;
}
public void execute(){
strategy.doPromotion();
}
}
4、编写客户端测试类 :
public class Test {
public static void main(String[] args) {
PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy());
activity618.execute();
activityllll.execute();
}
}
运行效果如下:
使用优惠券抵扣
返现,直接打款到支付宝账号
此时,小伙伴们会发现,如果把上面这段则试代码放到实际的业务场景其实并不实用。因为我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的,并不会一次性执行多种优惠。所以以,我们的代码通常会这样写 :
public class Test {
public static void main(String[] args) {
PromotionActivity promotionActivity = null;
String promotionKey = "COUPON";
if (StringUtils.equals(promotionKey, "COUPON")) {
promotionActivity = new PromotionActivity(new CouponStrategy());
} else if (StringUtils.equals(promotionKey, "CASHBACK")) {
promotionActivity = new PromotionActivity(new CashbackStrategy());
}
promotionActivity.execute();
}
}
这样改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略了。