软件设计指导思想
1. 可维护性Maintainability
修改功能,需要改动的地方越少,可维护性就越好
2. 可复用性Reusability
代码可以被以后重复使用
写出自己总结的类库
3. 可扩展性Extensibility/Scalability
添加功能无需修改原来代码
4. 灵活性flexibility/mobility/adaptability
代码接口可以灵活调用
设计原则
1. 单一职责原则
Single Responsibility Principle,简称SRP。
一个类别太大,负责单一的职责
Person PersonManager
高内聚,低耦合
2. 开闭原则
Open-Closed Principle,简称OCP。
对扩展开发,对修改关闭
尽量不修改原来代码的情况下,进行扩展
抽象化,多态是开闭原则的关键
3. 里氏替换原则
Liskov Substitution Principle,简称LSP。
所有使用父类的地方,必须能够透明的使用子类对象
4. 依赖倒置原则
Dependence Inversion Principle,简称DIP。
依赖抽象,而不是依赖具体
面向接口编程
5. 接口隔离原则
Interface Segregation Principle,简称ISP。
接口职责单一
每一个接口应该承担独立的角色,不干不该自己干的事情
- Flyable Runnable 不该合二为一
- 避免子类实现不需要实现的方法
- 需要对客户提供接口的时候,只需要暴露最小的接口
6. 迪米特法则
Law of Demeter,简称LoD。
降低耦合
尽量不要和其他类产生联系
在迪米特法则中,对于一个对象,非陌生类包括以下几类
- 当前对象本身(this)
- 以参数形式传入当前对象方法中的对象
- 当前对象的成员对象
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
- 当前对象所创建的对象
和其他类的耦合度变低
创建型模式(5种)
1. 单例(Singleton)
应用场景
只需要一个实例
比如各种Manager
比如各种Factory
步骤
1. 构造器私有化
2. 类的内部创建对象
3. 向外暴露一个静态的公共方法
1. 饿汉式
类加载到内存后,就实例化一个单例,JVM保证线程安全
优点:简单实用,推荐使用
缺点:不管用到与否,类加载时就完成实例化 Class.forName("") (话说你不用的,你装载它干啥)
class Manager01 {
private static final Manager01 INSTANCE = new Manager01();
private Manager01() {
}
public static Manager01 getInstance() {
return INSTANCE;
}
}
class Manager02 {
private static final Manager02 INSTANCE;
static {
INSTANCE = new Manager02();
}
private Manager02() { }
public static Manager02 getInstance() {
return INSTANCE;
}
}
2.懒汉式
用的时候加载,下面的方法实现,线程安全但是效率低
class Manager04 {
private static Manager04 INSTANCE;
private Manager04() { }
public static synchronized Manager04 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Manager04();
}
return INSTANCE;
}
}
3. 双重检测所(懒汉式)
这种懒汉式的方式,线程安全,效率高
class Manager06 {
private static volatile Manager06 INSTANCE;
private Manager06(){}
public static Manager06 getInstance() {
if (INSTANCE == null) {
synchronized (Manager06.class) {
if (INSTANCE == null) {
INSTANCE = new Manager06();
}
}
}
return INSTANCE;
}
}
4.静态内部内(懒汉式)
静态内部类方式,JVM保证单例,加载外部类时不会加载内部内,这样可以实现懒加载
class Manager07{
private static Manager07 INSTANCE;
private Manager07() { }
private static class Manager07Holder {
private static final Manager07 INSTANCE = new Manager07();
}
public static Manager07 getInstance() {
return Manager07Holder.INSTANCE;
}
}
5.枚举
不仅可以解决线程同步,还可以防止反序列化
不能反序列化的原因,是因为枚举类没有构造方法,即使拿到他的class文件,反序列化后的值依然是那个枚举值
enum Manager08 {
INSTANCE;
public static Manager08 getInstance() {
return INSTANCE;
}
}
工厂
任何可以产生对象的方法或类,都可以称为工厂
单例也是一种工厂
为什么有了new之后,还要有工厂?
灵活控制生产过程
权限,修饰,日志等
工厂分为静态工厂,工厂方法,抽象工厂
2. 工厂方法
任意指定交通工具
任意定制生产过程
public class FactoryMethod {
public static void main(String[] args) {
Moveable moveable = new CarFactory().create();
moveable.go();
Moveable moveable1 = new PlaneFactory().create();
moveable1.go();
}
}
//抽象产品
interface Moveable {
void go();
}
//抽象工厂
interface VehicleFactory {
Moveable create();
}
//具体产品
class Car implements Moveable {
@Override
public void go() {
System.out.println("car ... go ...");
}
}
//具体工厂
class CarFactory implements VehicleFactory {
@Override
public Moveable create() {
return new Car();
}
}
//具体产品
class Plane implements Moveable {
@Override
public void go() {
System.out.println("plane ... go ...");
}
}
//具体工厂
class PlaneFactory implements VehicleFactory {
@Override
public Moveable create() {
return new Plane();
}
}
3. 抽象工厂
任意定制产品一族
public class Main {
public static void main(String[] args) {
ModernFactory modernFactory = new ModernFactory();
Food food = modernFactory.createFood();
Weapon weapon = modernFactory.createWeapon();
Vehicle vehicle = modernFactory.createVehicle();
}
}
//抽象工厂
abstract class AbstractFactory {
abstract Food createFood();
abstract Weapon createWeapon();
abstract Vehicle createVehicle();
}
//抽象食物
abstract class Food {
abstract void printName();
}
//抽象武器
abstract class Weapon {
abstract void shoot();
}
//抽象交通工具
abstract class Vehicle {
abstract void go();
}
//具体食物(面包)
class Bread extends Food {
public void printName() {
System.out.println("wdm");
}
}
//具体武器(AK47)
class AK47 extends Weapon {
public void shoot() {
System.out.println("tututututu");
}
}
//具体交通工具(车)
class Car extends Vehicle {
public void go() {
System.out.println("car ... go ...");
}
}
//具体食物(毒蘑菇)
class MushRoot extends Food {
public void printName() {
System.out.println("dmg");
}
}
//具体武器(魔法棒)
class MagicStick extends Weapon {
public void shoot() {
System.out.println("diandian...");
}
}
//具体交通工具(扫帚)
class Broom extends Vehicle {
public void go() {
System.out.println("broom ... go ...");
}
}
//具体工厂(现代工厂)
class ModernFactory extends AbstractFactory {
@Override
Food createFood() {
return new Bread();
}
@Override
Weapon createWeapon() {
return new AK47();
}
@Override
Vehicle createVehicle() {
return new Car();
}
}
//具体工厂
class MagicFactory extends AbstractFactory {
@Override
Food createFood() {
return new MushRoot();
}
@Override
Weapon createWeapon() {
return new MagicStick();
}
@Override
Vehicle createVehicle() {
return new Broom();
}
}
4. 建造者(Builder)
分离复杂对象的创建和表示
同样的构建过程可以创建不同的表示
1. 建造者模式的优点
建造者模式的优点
■ 封装性,使用建造者模式可以使客户端不必知道产品内部组成的细节。
■ 建造者独立,容易扩展。
■ 便于控制细节风险,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
2. 建造者模式的使用场景
■ 相同的方法,不同的执行顺序,产生不同的结果时,可以采用建造者模式。
■ 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
■ 产品类非常复杂,或者产品类中的方法调用顺序不同产生了不同的效能,这个时候使用建造者模式。
■ 在对象创建过程中会使用到系统的一些其他对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景只能是一个补偿方法,因为一个对象不容易获得,而在设计阶段没有发现,要通过创建者模式柔化创建过程,本身已经违反设计的最初目标。
建造者模式关注的是零件类型和装配工艺顺序,这是与工厂方法模式最大的不同之处,虽然同为创建类模式,但是重点不同。
3. 代码
package com.ypf;
import lombok.Data;
// 产品(Product)角色
@Data
abstract class Computer {
private String type;//型号
private String cpu;//CPU
private String ram;//内存
private String hardDisk;//硬盘
private String monitor;//显示器
private String os;//操作系统
}
//具体产品T410
@Data
class T410 extends Computer {
private String graphicCard;//显卡
public T410() {
this.setType("ThinkPad T410");
}
@Override
public String toString() {
return "Computer{" +
"type='" + this.getType() + '\'' +
", cpu='" + this.getCpu() + '\'' +
", ram='" + this.getRam() + '\'' +
", hardDisk='" + this.getHardDisk() + '\'' +
", monitor='" + this.getMonitor() + '\'' +
", os='" + this.getOs() + '\'' +
", graphicCard='" + this.graphicCard + '\'' +
'}';
}
}
class X201 extends Computer {
public X201() {
this.setType("ThinkPad X201");
}
}
//抽象建造者(Builder)角色
interface ComputerBuilder {
ComputerBuilder buildCpu();//建造CPU
ComputerBuilder buildRam();//建造内存
ComputerBuilder buildHardDisk();//建造硬盘
ComputerBuilder buildGraphicCard();//建造显卡
ComputerBuilder buildMonitor();//建造显示器
ComputerBuilder buildOs();//建造操作系统
Computer getComputer();//得到建造好的计算机
}
//具体建造者(Concrete Builder)角色
class T410Builder implements ComputerBuilder {
private T410 t410 = new T410();
@Override
public ComputerBuilder buildCpu() {
t410.setCpu("i5-450");
return this;
}
@Override
public ComputerBuilder buildRam() {
t410.setRam("4GB 1333MHz");
return this;
}
@Override
public ComputerBuilder buildHardDisk() {
t410.setHardDisk("500GB 7200转");
return this;
}
@Override
public ComputerBuilder buildGraphicCard() {
t410.setGraphicCard("Nvidia NVS 3100M");
return this;
}
@Override
public ComputerBuilder buildMonitor() {
t410.setMonitor("14英寸 1280*800");
return this;
}
@Override
public ComputerBuilder buildOs() {
t410.setOs("Windows 6 旗舰版");
return this;
}
@Override
public Computer getComputer() {
return t410;
}
}
//具体建造者(Concrete Builder)角色
class X201Builder implements ComputerBuilder {
private X201 x201 = new X201();
@Override
public ComputerBuilder buildCpu() {
x201.setCpu("i3-350");
return this;
}
@Override
public ComputerBuilder buildRam() {
x201.setRam("2GB 1333MHz");
return this;
}
@Override
public ComputerBuilder buildHardDisk() {
x201.setHardDisk("250GB 5400转");
return this;
}
@Override
public ComputerBuilder buildGraphicCard() {
//无显卡
return this;
}
@Override
public ComputerBuilder buildMonitor() {
x201.setMonitor("12英寸 1280*800");
return this;
}
@Override
public ComputerBuilder buildOs() {
x201.setOs("Window10 Home版");
return this;
}
@Override
public Computer getComputer() {
return x201;
}
}
//导演者(Director)角色
class ComputerDirector {
ComputerBuilder builder;
//构造T410型计算机
public T410 constructT410() {
builder = new T410Builder();
builder.buildCpu().
buildRam().
buildHardDisk().
buildGraphicCard().
buildMonitor().
buildOs();
return (T410) builder.getComputer();
}
//构造X201型计算机
public X201 constructX201() {
builder = new X201Builder();
builder.buildCpu().
buildRam().
buildHardDisk().
buildMonitor().
buildOs();
return (X201) builder.getComputer();
}
}
public class Main {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
T410 t410 = director.constructT410();
System.out.println("t410 = " + t410);
System.out.println("----------------");
X201 x201 = director.constructX201();
System.out.println("x201 = " + x201);
}
}
5. 原型(Prototype)
java中自带原型模式
实现原型模式需要实现标记型接口Cloneable
一般会重写clone()方法
如果只是重写clone(),而没有实现接口,调用时会抛异常
一般用于一个对象的属性已经确定,需要产生很多相同对象的时候
需要区分深克隆和浅克隆
深克隆
属性如果是引用类型,引用类型也需要克隆
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person();
Person p2 = (Person) p1.clone();
System.out.println(p1.age == p2.age);
p2.age = 15;
System.out.println(p1.age == p2.age);
System.out.println("==================");
System.out.println(p1.location.street == p2.location.street);
p2.location.street = "xa";
System.out.println(p1.location.street == p2.location.street);
}
}
class Person implements Cloneable {
int age = 8;
int score = 100;
Location location = new Location("bj", 22);
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
person.location = (Location) location.clone();
return person;
}
}
class Location implements Cloneable{
String street;
int rootNo;
public Location(String street, int rootNo) {
this.street = street;
this.rootNo = rootNo;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Location{" +
"street='" + street + '\'' +
", rootNo=" + rootNo +
'}';
}
}
结构型模式(7种)
1. 代理
代理分为静态代理和动态代理
静态代理
代码
package com.ypf;
public class Main {
public static void main(String[] args) {
Subject subject = new RealSubject();
subject = new ProxySubject(subject);
subject.request();
}
}
//抽象主题
interface Subject {
//定义一个请求方法
void request();
}
//真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("业务处理逻辑");
}
}
//代理主题
class ProxySubject implements Subject {
private Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
//实现请求方法
@Override
public void request() {
this.beforeRequest();
subject.request();
this.afterRequest();
}
private void beforeRequest() {
System.out.println("请求前的操作");
}
private void afterRequest() {
System.out.println("请求后的操作");
}
}
JDK动态代理
缺陷:必须要有接口
执行过程
当我们调用newProxyInstance的时候,调用的是Proxy的newProxyInstance,在这个newProxyInstance中通过一系列的方式,生成了$Proxy0这个类,生成了一个对象,构造器里,往里面传递了一个InvocationHandler对象。
接下来,既然生成了,就可以调用它了
调用了move方法,实际调用的是$Proxy0的move方法,因为$Proxy0才是我们生成的对象,在这个方法中调用的是InvocationHandler的invoke方法
代码
package com.ypf;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Subject subject = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
RealSubject.class.getInterfaces(),
new LogHander(realSubject));
subject.request();
}
}
//抽象主题
interface Subject {
//定义一个请求方法
void request();
}
//真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("业务处理逻辑");
}
}
//InvocationHandler
class LogHander implements InvocationHandler {
private Subject subject;
public LogHander(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("请求前的操作");
Object o = method.invoke(subject, args);
System.out.println("请求后的操作");
return o;
}
}
生成的代理类(使用 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 可以看到生成后的代理类)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.ypf;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void request() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.ypf.Subject").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
cglib动态代理
package com.ypf;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new LogMethodInterceptor());
RealSubject subject = (RealSubject) enhancer.create();
subject.operation();
}
}
class RealSubject {
public void operation() {
System.out.println("业务操作");
}
}
class LogMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象
* @param method 原始方法
* @param objects 方法参数
* @param methodProxy 代理方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after");
return result;
}
}
2. 装饰
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活
1. 装饰模式的优缺点
装饰模式的优点
■ 装饰类和被装饰类可以独立发展,而不会相互耦合。即 Component 类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。
■ 装饰模式是继承关系的一个替代方案。装饰类Decorator,不管装饰多少层,返回的对象还是Component。
■ 装饰模式可以动态地扩展一个实现类的功能。
装饰模式的缺点:多层的装饰是比较复杂的。
2. 装饰模式的使用场景
■ 需要扩展一个类的功能,或给一个类增加附加功能。
■ 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
■ 需要为一批类进行改装或加装功能。
装饰模式是对继承的有力补充。单纯使用继承时,在一些情况下就会增加很多子类,而且灵活性差,维护也不容易。装饰模式可以替代继承,解决类膨胀的问题,如Java基础类库中的输入输出流相关的类大量使用了装饰模式。
3. 代码实现
package com.ypf;
public class Main {
public static void main(String[] args) {
Component component = new ConcreteComponent();
//进行修饰
component = new ConcreteDecorator(component);
component.operation();
}
}
//抽象构件
interface Component {
void operation();
}
//具体构件
class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("业务代码");
}
}
//修饰角色
abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
this.component.operation();
}
}
//具体修饰
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
//定义自己的方法
private void method() {
System.out.println("修饰");
}
//重写operation方法
public void operation() {
this.method();
super.operation();
}
}
3. 适配器
适配器模式(Adapter Pattern)又叫做变压器模式,变压器把一种电压变换为另一种电压。
将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。适配器模式就是将一个接口或类转换成其他的接口或类,适配器相当于一个包装器
1. 适配器模式的优点
适配器模式的优点有以下几个方面。
■ 适配器模式可以让两个没有任何关系的类在一起运行。
■ 增加了类的透明性。
■ 提高类的复用度。
■ 增强代码的灵活性。
2. 适配器模式的使用场景
修改一个已经投产中的系统时,需要对系统进行扩展,此时使用一个已有的类,但这个类不符合系统中的接口,这时使用适配器模式是最合适的,它可以将不符合系统接口的类进行转换,转换成符合系统接口的、可以使用的类。
3.代码
package com.ypf;
public class Main {
public static void main(String[] args) {
//适配器模式应用
Target target = new Adapter();
target.request();
}
}
//源角色
class Adaptee {
public void specificRequest() {
System.out.println("原有业务处理");
}
}
//目标角色接口
interface Target {
public void request();
}
//适配器角色
class Adapter extends Adaptee implements Target {
@Override
public void request() {
super.specificRequest();
}
}
4. 组合模式
组合模式(Composite Pattern)也叫合成模式,用来描述部分与整体的关系。
将对象组合成树形结构以表示“部分—整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
1. 组合模式的优缺点
组合模式的优点
■ 高层模块调用简单。一棵树形机构中的所有节点都是 Component,局部和整体对调用者来说没有任何区别,即高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
■ 节点自由增加。使用组合模式后,如果想增加一个树枝节点、树叶节点只需要找到其父节点即可。
组合模式的缺点
■ 不易控制树枝构件的类型;
■ 不易使用继承的方法来增加新的行为。
2. 组合模式的使用场景
■ 需要描述对象的部分和整体的等级结构,如树形菜单、文件和文件夹管理。
■ 需要客户端忽略个体构件和组合构件的区别,平等对待所有的构件。
组合模式也是应用广泛的一种设计模式,例如,Java基础类库的swing部分中就大量使用了组合模式,大部分控件都是 JComponent 的子类,同时其add()方法又可向界面添加JComponent类型的控件,从而使得使用者可以以统一的方式操作各种控件。
3. 代码实现
//抽象构件角色
public interface Component {
public void operation();
}
//定义叶子构架
public class Leaf implements Component{
@Override
public void operation() {
System.out.println("叶子构件业务逻辑代码");
}
}
//定义树枝构件
public class Branch implements Component {
//构件容器
private List<Component> componentList = new ArrayList<>();
//添加构件
public void add(Component component) {
this.componentList.add(component);
}
//删除构件
public void remove(Component component) {
this.componentList.remove(component);
}
//获取子构件
public List<Component> getChild() {
return this.componentList;
}
@Override
public void operation() {
System.out.println("树枝构件。。。业务逻辑代码");
}
}
public class Client {
public static void main(String[] args) {
//创建一个根节点
Branch root = new Branch();
root.operation();
//创建树枝节点
Branch branch = new Branch();
//创建叶子节点
Leaf leaf = new Leaf();
//构件树形结构
root.add(branch);
branch.add(leaf);
display(root);
}
//遍历树
public static void display(Branch root) {
for (Component component : root.getChild()) {
if (component instanceof Leaf) {
component.operation();
} else {
component.operation();
display((Branch) component);
}
}
}
}
5. 桥接(Bridge)
分离抽象和具体
用聚合方式(桥)连接抽象和具体
将抽象和实现解耦,使得两者可以独立地变化。
1. 桥梁模式的优点
桥梁模式虽然是一个使用频率不高的模式,但是熟悉该模式对于理解面向对象的设计原则,包括开闭原则都很有帮助,有助于形成正确的设计思想和培养良好的设计风格。
桥梁模式的优点有以下几个方面。
■ 抽象和实现分离是桥梁模式的主要特点,是为了解决继承的缺点而提出的设计模式。在该模式下,实现可以不受抽象的约束,不用绑定在一个固定的抽象层次上。
■ 实现对客户透明,客户端不用关心细节的实现,它已经由抽象层通过聚合关系完成了封装。
■ 提高灵活性和扩展性。
2. 桥梁模式的使用场合
使用桥梁模式的场合如下。
■ 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
■ 设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
■ 一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
■ 不希望或不适合使用继承的场合。继承具有强入侵性质,即父类有的方法,子类必须有;而桥梁模式是弱关联关系。因此对于比较明确不发生变化的,则可以通过继承完成;若不能确定是否会发生变化,则通过桥梁模式来解决。
注意 使用桥梁模式时主要考虑如何拆分抽象和实现,桥梁模式的意图还是对变化的封装,尽量把可能变化的因素封装到最细、最小的逻辑单元中,避免风险扩散。
3. 代码和类图
package com.ypf;
public class Main {
public static void main(String[] args) {
Color color = new Green();
AbstractShape shape = new Square(color);
shape.draw();
}
}
//抽象化(Abstraction)角色 图形
abstract class AbstractShape {
Color color;
public AbstractShape(Color color) {
this.color = color;
}
public abstract void draw();
}
//实现化(Implementor)角色 颜色
interface Color {
String getColor();
}
//修正抽象化(RefinedAbstraction)角色 圆形
class Circle extends AbstractShape {
public Circle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("使用" + color.getColor() + "画圆形");
}
}
//修正抽象化(RefinedAbstraction)角色 正方形
class Square extends AbstractShape {
public Square(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("使用" + color.getColor() + "正方形");
}
}
//具体实现化(ConcreteImplementor)角色
class Red implements Color {
@Override
public String getColor() {
return "红色";
}
}
//具体实现化(ConcreteImplementor)角色
class Green implements Color {
@Override
public String getColor() {
return "绿色";
}
}
6. 外观
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次的接口,使得子系统更易使用。外观模式注重“统一的对象”,即提供一个访问子系统的接口,只有通过该接口(Façade)才能允许访问子系统的行为发生
1. 外观模式的优点外观模式的优点有以下几个方面。
■ 减少系统的相互依赖,所有的依赖都是对Façade对象的依赖,与子系统无关。
■ 提高灵活性,不管子系统内部如何变化,只要不影响Facade对象,任何活动都是自由的。
■ 提高安全性,Facade中未提供的方法,外界就无法访问,提高系统的安全性。注意外观模式最大的缺点是不符合开闭原则,对修改关闭,对扩展开放。
2. 外观模式的使用场景使用外观模式的典型场景如下。
■ 为一个复杂的模块或子系统提供一个供外界访问的接口。
■ 子系统相对独立,外界对子系统的访问只要黑箱操作即可。
■ 预防风险扩散,使用Façade进行访问操作控制。
7. 享元模式
享元模式(Flyweight Pattern)是池技术的重要实现方式,可以降低大量重复的、细粒度的类在内存中的开销。
使用共享对象可有效地支持大量的细粒度的对象。
享元模式是以共享的方式高效地支持大量的细粒度对象。享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。
■ 内部状态是存储在享元对象内部的、可以共享的信息,并且不会随环境改变而改变。
■ 外部状态是随环境改变而改变且不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。
1. 享元模式的优缺点
享元模式的优点在于大幅减少内存中对象的数量,降低程序内存的占用,提高性能。但是,相应付出的代价也很高。
■ 享元模式增加了系统的复杂性,需要分出外部状态和内部状态,而且内部状态具有固化特性,不应该随外部状态改变而改变,这使得程序的逻辑复杂化。
■ 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间变长。
2. 享元模式的使用场景
■ 系统中有大量的相似对象,这些对象耗费大量的内存。
■ 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,即对象没有特定身份。
■ 需要缓冲池的场景。
Java基础类库中大量使用了享元模式,如String、Integer、Boolean、Character等类都通过享元模式提供了内部的池化机制。
3. 代码
public class Main {
public static void main(String[] args) {
Flyweight flyweight = FlyweightFactory.getFlyweight("intrinsicState");
flyweight.operation("extrinsicState");
}
}
//抽象享元
interface Flyweight {
//业务方法
public abstract void operation(String extrinsicState);
}
//具体享元
class ConcreteFlyweight implements Flyweight {
private String intrinsicState;//内部状态
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("内部状态:" + intrinsicState
+ ",外部状态:" + extrinsicState);
}
}
//享元工厂
class FlyweightFactory {
private static Map<String, Flyweight> pool = new HashMap<>();
private FlyweightFactory() {}//已有构造方法
public static Flyweight getFlyweight(String intrinsicState) {
Flyweight flyweight = pool.get(intrinsicState);
if (flyweight == null) {
flyweight = new ConcreteFlyweight(intrinsicState);
pool.put(intrinsicState, flyweight);
}
return flyweight;
}
}
行为型模式(11种)
1. 模板方法(TemplateMethod)
也即钩子函数
运用情况,JUC中的AQS实现的锁中很多基于这种设计模式,类加载机制中
public class TemplateMethod {
public static void main(String[] args) {
Template template = new TemplateImpl();
template.m();
}
}
abstract class Template {
public void m() {
method1();
method2();
}
abstract void method1();
abstract void method2();
}
class TemplateImpl extends Template {
@Override
void method1() {
System.out.println("method1");
}
@Override
void method2() {
System.out.println("method2");
}
}
2. 命令
命令模式(Command Pattern)又称为行动(Action)模式或交易(Transaction)模式。
将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
1. 命令模式的优缺点
命令模式的优点
■ 类间解耦。调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需要调用Command中的execute()方法即可,不需要了解是哪个接收者执行。
■ 可扩展性。Command 的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。
■ 命令模式结合其他模式会更优秀。命令模式可以结合责任链模式,实现命令族解析任务,结合模板方法模式,则可以减少Command子类的膨胀问题。
命令模式的缺点
■ 使用命令模式可能会导致系统中出现过多的具体命令类,因此需要在项目中慎重考虑使用。
2. 使用命令模式的典型场景
■ 使用命令模式作为“回调”在面向对象系统中的替代。“回调”讲的便是将一个函数登记上,然后在以后调用此函数。
■ 需要在不同的时间指定请求、将请求排队。
■ 系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销时,可以调用undo()方法,将命令所产生的效果撤销。
■ 需要将系统中所有的数据更新操作保存到日志里,以便在系统崩溃时,可以根据日志读回所有的数据更新命令,重新调用 execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
■ 一个系统需要支持交易(transaction)。一个交易结构封装了一组数据更新命令。使用命令模式来实现交易结构可以使系统增加新的交易类型。
3. 代码
package com.ypf;
public class Main {
public static void main(String[] args) {
//调用者
Invoker invoker = new Invoker();
//接收者
Receiver receiver = new Receiver();
//定义一个发送给接收者的命令
Command command = new ConcreteCommand(receiver);
//执行
invoker.setCommand(command);
invoker.action();
}
}
//调用者
class Invoker {
private Command command;
//接收命令
public void setCommand(Command command) {
this.command = command;
}
//执行命令
public void action() {
this.command.execute();
}
}
//命令
interface Command {
//执行命令的方法
void execute();
}
//具体命令
class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
//执行方法
@Override
public void execute() {
this.receiver.action();
}
}
//接受者
class Receiver {
//行动方法
public void action() {
System.out.println("执行动作");
}
}
3. 责任链
使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。责任链模式的重点是在“链”上,由一条链去处理相似的请求,在链中决定谁来处理这个请求,并返回相应的结果。
1. 责任链模式的优缺点
责任链模式的优点
■ 责任链模式将请求和处理分开,请求者不知道是谁处理的,处理者可以不用知道请求的全貌。
■ 提高系统的灵活性。
责任链模式的缺点
■ 降低程序的性能,每个请求都是从链头遍历到链尾,当链比较长的时候,性能会大幅下降。
■ 不易于调试,由于采用了类似递归的方式,调试的时候逻辑比较复杂。
注意 责任链中的节点数量需要控制,避免出现超长链的情况,这就需要设置一个最大的节点数量,超过则不允许增加节点,避免无意识地破坏系统性能。
2. 责任链模式的应用场景
责任链模式是一种常见的模式,Struts2的核心控件FilterDispatcher是一个Servlet过滤器,该控件就是采用责任链模式,可以对用户请求进行层层过滤处理。责任链模式在实际的项目中使用的比较多,其典型的应用场景如下。
■ 一个请求需要一系列的处理工作。
■ 业务流的处理,例如,文件审批。
■ 对系统进行补充扩展。
3. 代码
public class Main {
public static void main(String[] args) {
//创建一个责任链
Player player = new PlayerA(new PlayerB(new PlayerC(new PlayerD(null))));
//设置3下停止
player.handle(4);
}
}
@Data
abstract class Player {
private Player successor;
public abstract void handle(int idx);
//传递给下一个
public void next(int idx) {
if (successor != null) {
successor.handle(idx);
} else {
System.out.println("游戏结束");
}
}
}
class PlayerA extends Player {
//构造函数
public PlayerA(Player successor) {
this.setSuccessor(successor);
}
//实现handle方法
@Override
public void handle(int idx) {
if (idx == 1) {
System.out.println("PlayerA 处理");
} else {
System.out.println("PlayerA 把问题向下传");
next(idx);
}
}
}
class PlayerB extends Player {
//构造函数
public PlayerB(Player successor) {
this.setSuccessor(successor);
}
//实现handle方法
@Override
public void handle(int idx) {
if (idx == 2) {
System.out.println("PlayerB 处理");
} else {
System.out.println("PlayerB 把问题向下传");
next(idx);
}
}
}
class PlayerC extends Player {
//构造函数
public PlayerC(Player successor) {
this.setSuccessor(successor);
}
//实现handle方法
@Override
public void handle(int idx) {
if (idx == 3) {
System.out.println("PlayerC 处理");
} else {
System.out.println("PlayerC 把问题向下传");
next(idx);
}
}
}
class PlayerD extends Player {
//构造函数
public PlayerD(Player successor) {
this.setSuccessor(successor);
}
//实现handle方法
@Override
public void handle(int idx) {
if (idx == 4) {
System.out.println("PlayerD 处理");
} else {
System.out.println("PlayerD 把问题向下传");
next(idx);
}
}
}
4. 策略(Strategy)
策略模式封装的是做一件事情的时候,不同的执行方式
定义一组算法,将每个算法都封装起来,并且使他们之间可以互换。
其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,是的算法可以在不影响客户端的情况下发生变化
应用场景
1. 一个对象,不同的排序方式
2. 游戏中,子弹打出的不同策略
public class Strategy {
public static void main(String[] args) {
Dog[] dogs = {
new Dog(1, "q", 1),
new Dog(2, "a", 5),
new Dog(3, "w", 3),
new Dog(4, "f", 7),
};
//根据策略1排序
Arrays.sort(dogs, new DogCompare1());
System.out.println(Arrays.toString(dogs));
//根据策略2排序
Arrays.sort(dogs, new DogCompare2());
System.out.println(Arrays.toString(dogs));
//根据策略3排序
Arrays.sort(dogs, new DogCompare3());
System.out.println(Arrays.toString(dogs));
}
}
@Data
@AllArgsConstructor
class Dog {
int id;
String name;
int age;
}
//根据id比较大小
class DogCompare1 implements Comparator<Dog> {
@Override
public int compare(Dog o1, Dog o2) {
if (o1.getId() < o2.getId()) {
return -1;
} else if (o1.getId() > o2.getId()) {
return 1;
} else {
return 0;
}
}
}
//根据name比较大小
class DogCompare2 implements Comparator<Dog> {
@Override
public int compare(Dog o1, Dog o2) {
return o1.getName().compareTo(o2.getName());
}
}
//根据age比较大小
class DogCompare3 implements Comparator<Dog> {
@Override
public int compare(Dog o1, Dog o2) {
if (o1.getAge() < o2.getAge()) {
return -1;
} else if (o1.getAge() > o2.getAge()) {
return 1;
} else {
return 0;
}
}
}
5. 迭代器
迭代器模式(Iterator Pattern)是最常被使用的几个模式之一,被广泛地应用到 Java 的API中。例如,Java的集合(Collection)框架中,就广泛使用迭代器来遍历集合中的元素。
提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。迭代器是为容器服务的,容器是指用来容纳其他对象的对象,例如,Collection集合类型、Set类等。迭代器模式便于遍历和访问容器中的各个元素
1. 迭代器模式的优缺点
迭代器模式的优点
■ 迭代器模式简化了访问容器元素的操作,具备一个统一的遍历接口。
■ 封装遍历算法,使算法独立于聚集角色。客户无须知道聚集对象的类型,即使聚集对象的类型发生变化,也不会影响遍历过程。
迭代器模式的缺点
■ 迭代器模式给使用者一个序列化的错觉,从而产生错误。
■ 迭代器的元素都是Object类型,没有类型特征(JDK1.5后通过引入泛型可以解决此问题)。
2. 迭代模式的应用场景
迭代器现在被广泛地应用,甚至已经成为一个最基础的工具。有些人建议将迭代器模式从 23 种模式中删除,其原因就是迭代器模式太普通了,已经融合到各个语言和工具中。在Java语言中,从JDK1.2版本开始,增加了java.util.Iterator接口,并将Iterator应用到各个聚集类(Collection)中,如ArrayList、Vector、Stack、HashSet 等集合类都实现了 iterator()方法,返回一个迭代器 Iterator,便于对集合中的元素进行遍历。也正因为 Java 将迭代器模式已经融合到基本的API中,使我们在项目中无须在独立地写迭代器,直接使用即可,这样既轻松又便捷。
注意 在Java开发中,尽量不要自己写迭代器模式,使用Java API提供的Iterator一般就能满足项目的要求。
6. 中介者
中介者模式(Mediator)也称调停者模式,是一种比较简单的模式。
用一个中介对象封装一系列对象(同事)的交互,中介者使各对象不需要显式地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
1. 中介者模式的优缺点
中介者模式的优点
■ 减少类间的依赖,将原有的一对多的依赖变成一对一的依赖,使得对象之间的关系更易维护和理解。
■ 避免同事对象之间过度耦合,同事类只依赖于中介者,使同事类更易被复用,中介类和同事类可以相对独立地演化。
■ 中介者模式将对象的行为和协作抽象化,将对象在小尺度的行为上与其他对象的相互作用分开处理。
中介者模式的缺点
■ 中介者模式降低了同事对象的复杂性,但增加了中介者类的复杂性。
■ 中介者类经常充满了各个具体同事类的关系协调代码,这种代码是不能复用的。
2. 中介者模式的注意事项
中介者模式简单,但是简单不代表容易使用,它很容易被误用和滥用。在面向对象的编程中,对象和对象之间必然会有依赖关系,如果某个类和其他类没有任何相互依赖的关系,那这个类就是一个孤岛,在系统中就没有存在的必要。一个类依赖多个类的情况也是正常的,存在就有其合理性,并不是只要有多个依赖关系就考虑使用中介者模式。
在下列几种情况下不适合使用中介者模式。
■ 不应当在责任划分混乱时使用。通常情况下,一个初级设计师在面向对象的技术不熟悉时,会使一个系统在责任的分割上发生混乱。责任分割的混乱会使得系统中的对象与对象之间产生不适当的复杂关系。
■ 不应当对数据类和方法类使用。初级设计师常常会设计出这样一种系统,让一系列类只含有数据,另一些类只含有方法。例如,描述一个客户时,这些设计师首先设计出一个“客户数据”类,只含有客户数据;另外再设计一个类叫做“管理类”,含有操作客户以及此客户购买公司产品、付账的方法。管理类自然会涉及其他的类,诸如产品数据类、订单数据类、付账数据类、应收账数据类等。这不是一种好的设计方式,也不是中介者模式。
■ 正确理解封装。封装首先是行为,以及行为所涉及的状态的封装。行为与状态是不应该分割开的。中介者模式的用途是管理很多的对象的相互作用,以便使这些对象可以专注于自身的行为。
3. 代码与类图
package com.ypf;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
MarriageAgency agency = new MarriageAgencyImpl();
Person man1 = new Man("John", 20, 18, agency);
Person man2 = new Man("Mike", 27, 25, agency);
Person woman1 = new Woman("Mary", 25, 27, agency);
Person woman2 = new Woman("Jane", 20, 22, agency);
man1.findPartner();
man2.findPartner();
}
}
//抽象婚姻中介所
interface MarriageAgency {
void register(Person person);//注册会员
void pair(Person person);//为person配对
}
//具体婚姻中介所
class MarriageAgencyImpl implements MarriageAgency {
List<Man> men = new ArrayList<>();//男会员
List<Woman> women = new ArrayList<>();//女会员
@Override
public void register(Person person) {
if (person.sex == Sex.MALE) {
men.add((Man) person);
} else if (person.sex == Sex.FEMALE) {
women.add((Woman) person);
}
}
@Override
public void pair(Person person) {
if (person.sex == Sex.MALE) {
for (Woman w : women) {
if (person.requestAge == w.age) {
System.out.println(person.name + "和" + w.name + "配对成功");
return;
}
}
} else if (person.sex == Sex.FEMALE) {
for (Man m : men) {
if (person.requestAge == m.age) {
System.out.println(person.name + "和" + m.name + "配对成功");
return;
}
}
}
System.out.println("没有为" + person.name + "找到合适的对象");
}
}
enum Sex {
MALE, FEMALE;
}
//人的抽象类
abstract class Person {
String name;//姓名
int age;//年龄
Sex sex;//性别
int requestAge;//要求对象的年龄
MarriageAgency agency;//婚姻中介
public Person(String name, int age, Sex sex, int requestAge, MarriageAgency agency) {
this.name = name;
this.age = age;
this.sex = sex;
this.requestAge = requestAge;
this.agency = agency;
agency.register(this);//注册会员
}
//寻找对象
public void findPartner() {
agency.pair(this);
}
}
//男人类
class Man extends Person {
public Man(String name, int age, int requestAge, MarriageAgency agency) {
super(name, age, Sex.MALE, requestAge, agency);
}
}
//女人类
class Woman extends Person {
public Woman(String name, int age, int requestAge, MarriageAgency agency) {
super(name, age, Sex.FEMALE, requestAge, agency);
}
}
7. 观察者模式(类图要修改)
观察者模式(Observer Pattern)也称发布订阅模式,它是一种在项目中经常使用的模式。
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
1. 观察者模式的优缺点
观察者模式优点
■ 观察者和被观察者之间是抽象耦合。被观察者角色所知道的只是一个具体观察者集合,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体的观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密的耦合在一起,因此它们可以属于不同的抽象化层次,且都非常容易扩展。
■ 支持广播通信。被观察者会向所有登记过的观察者发出通知,这就是一个触发机制,形成一个触发链。
观察模式的缺点
■ 如果一个主题有多个直接或间接的观察者,则通知所有的观察者会花费很多时间,且开发和调试都比较复杂。
■ 如果在主题之间有循环依赖,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式时要特别注意这一点。
■ 如果对观察者的通知是通过另外的线程进行异步投递,系统必须保证投递的顺序执行。
■ 虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有提供相应的机制使观察者知道所观察的对象是如何发生变化。
2. 观察者模式的应用场景
■ 关联行为场景。
■ 事件多级触发场景。
■ 跨系统的消息交换场景,如消息队列的处理机制。
3. 观察者模式的注意事项
■ 广播链的问题。一个观察者可以有双重身份,既是观察者也是被观察者,广播链一旦建立,逻辑就比较复杂,可维护性非常差。一般在一个观察者模式中最多出现一个对象既是观察者也是被观察者,这样消息最多转发一次(传递两次),较易控制。
■ 异步处理的问题。异步处理就要考虑线程安全和队列的问题。注意观察者广播链和责任链模式的最大区别就是观察者广播链在传播的过程中,消息是随时更改的,是由相邻的两个节点协商的消息结构;而责任链模式在消息传递过程中,消息是保持不变的,如果要改变,也只有在原有消息上进行修正。
4. 代码
public class Main {
public static void main(String[] args) {
//创建一个主题对象
ConcreteSubject subject = new ConcreteSubject();
//创建一个观察者
Observer observer = new ConcreteObserver();
//登记观察者
subject.add(observer);
//改变状态
subject.change();
}
}
//抽象观察者
interface Observer {
//更新方法
void operation();
}
//具体观察者
class ConcreteObserver implements Observer {
@Override
public void operation() {
System.out.println("收到通知,并进行处理");
}
}
//抽象主题
interface Subject {
//增加一个新的观察者
void add(Observer observer);
//删除一个观察者
void delete(Observer observer);
//通知所有登记过的观察者对象
void notifyObserver();
}
//具体主题
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void add(Observer observer) {
observers.add(observer);
}
@Override
public void delete(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObserver() {
for (Observer observer : observers) {
observer.operation();
}
}
//业务方法,改变状态
public void change() {
this.notifyObserver();
}
//返回观察者集合
public List<Observer> observers() {
return this.observers;
}
}
8. 备忘录
备忘录模式(Memento Pattern)又称为快照(Snapshot)模式或Token模式。
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后就可以将该对象恢复到原先保存的状态。通俗地说,备忘录模式就是将一个对象进行备份,提供一种程序数据的备份方法
1. 备忘录模式的应用场景
■ 需要保存和恢复数据的相关状态场景。
■ 提供一个可回滚的操作。
■ 需要监控副本的场景。例如,监控一个对象的属性,但是监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大,因此一般做法是备份一个主线程中的对象,然后由分析程序来分析。
■ 数据库连接的事务管理使用的就是备忘录模式。
2. 备忘录模式的注意事项
■ 备忘录的生命周期,备忘录创建出来就要在最近的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理。
■ 备忘录的性能。不要在频繁建立备份的场景中使用备忘录模式,例如,for循环中,一是控制不了备忘录建立的对象数量;二是大对象的建立是要消耗资源的,需要考虑系统的性能。因此,如果出现这样的代码,设计师应该修改架构。
3. 代码
package com.ypf;
import lombok.AllArgsConstructor;
import lombok.Data;
public class Main {
public static void main(String[] args) {
//创建发起人
Originator originator = new Originator();
//定义负责人
Caretaker caretaker = new Caretaker();
//创建一个备忘录
caretaker.setMemento(originator.createMemento());
//恢复一个备忘录
originator.restoreMemento(caretaker.getMemento());
}
}
//发起人角色
@Data
class Originator {
private String state;
//创建备忘录
public Memento createMemento() {
return new Memento(this.state);
}
//恢复一个备忘录
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
//备忘录角色
@Data
@AllArgsConstructor
class Memento {
//发起人内部状态
private String state;
}
//负责人角色
@Data
class Caretaker {
//备忘录对象
private Memento memento;
//一般是一大堆的备忘录对象
}
9. 访问者
访问者模式(Visitor Pattern)的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改,接受这个操作的数据结构则可以保持不变。
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
1. 访问者模式的优缺点
访问者模式的优点
■ 访问者模式使得增加新的操作变得很容易,增加新的操作只需要增加新的访问者类。
■ 访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个元素类中。
■ 访问者模式可以跨过几个类的等级结构访问属于不同等级结构的成员类。
■ 累积状态。每一个单独的访问者对象都集中了相关的行为,从而也就可以在访问的过程中将执行操作的状态积累在自己内部,而不是分散到很多的元素对象中,益于系统的维护。
访问者模式的缺点
■ 增加新的元素类变得很困难。每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
■ 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这隐含了一个对所有元素对象的要求,即必须暴露一些自己的操作和内部状态,否则访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使得这些状态不再存储在元素对象中,破坏了类的封装性。
■ 违背了依赖倒置原则。访问者依赖的是具体的元素,而不是抽象的元素,这破坏了依赖倒置的原则,特别是在面向对象的编程中,抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。
2. 访问者模式的应用场景
■ 一个对象结构包含很多类对象,它们有不同的接口,当对这些对象实施依赖于具体类的操作时,即使用迭代器模式不能胜任的场景下,可以采用访问者模式。
■ 需要对一个对象结构中的对象进行很多不同并且不相关的操作,避免操作污染类。
■ 业务规则要求遍历多个不同的对象,这本身也是访问者模式的出发点,迭代器模式只能访问同类或同接口的数据,而访问者模式是对迭代器模式的扩充,可以遍历不同的对象,执行不同的操作。
3. 代码
package com.ypf;
public class Main {
public static void main(String[] args) {
PersonelVisitor visitor = new PersonelVisitor();
Computer computer = new Computer();
computer.accept(visitor);
System.out.println(visitor.totalPrice);
}
}
//结构对象(Object Structure)角色
class Computer {
ComputerPart cpu = new Cpu();
ComputerPart memory = new Memory();
ComputerPart board = new Board();
public void accept(Visitor visitor) {
this.cpu.accept(visitor);
this.memory.accept(visitor);
this.board.accept(visitor);
}
}
//抽象元素(Element)角色
abstract class ComputerPart {
abstract void accept(Visitor visitor);
abstract double getPrice();
}
//具体元素(Concrete Element)角色
class Cpu extends ComputerPart {
@Override
void accept(Visitor visitor) {
visitor.visitCpu(this);
}
@Override
double getPrice() {
return 500;
}
}
//具体元素(Concrete Element)角色
class Memory extends ComputerPart {
@Override
void accept(Visitor visitor) {
visitor.visitMemory(this);
}
@Override
double getPrice() {
return 300;
}
}
//具体元素(Concrete Element)角色
class Board extends ComputerPart {
@Override
void accept(Visitor visitor) {
visitor.visitBoard(this);
}
@Override
double getPrice() {
return 200;
}
}
//抽象访问者(Visitor)角色
interface Visitor {
void visitCpu(Cpu cpu);
void visitMemory(Memory memory);
void visitBoard(Board board);
}
//具体访问者(Concrete Visitor)角色
class PersonelVisitor implements Visitor {
double totalPrice = 0.0;
@Override
public void visitCpu(Cpu cpu) {
totalPrice += cpu.getPrice() * 0.9;
}
@Override
public void visitMemory(Memory memory) {
totalPrice += memory.getPrice() * 0.85;
}
@Override
public void visitBoard(Board board) {
totalPrice += board.getPrice() * 0.95;
}
}
//具体访问者(Concrete Visitor)角色
class CorpVisitor implements Visitor {
double totalPrice = 0.0;
@Override
public void visitCpu(Cpu cpu) {
totalPrice += cpu.getPrice() * 0.6;
}
@Override
public void visitMemory(Memory memory) {
totalPrice += memory.getPrice() * 0.75;
}
@Override
public void visitBoard(Board board) {
totalPrice += board.getPrice() * 0.75;
}
}
10. 状态
状态模式(State Pattern)又称为状态对象模式,该模式允许一个对象在其内部状态改变时改变其行为。
当一个对象内在状态改变时允许改变行为,这个对象看起来像改变了其类型。状态模式的核心是封装,状态的变更引起行为的变动,从外部看来就好像该对象对应的类发生改变一样。
1. 状态模式的优缺点
状态模式的优点
■ 结构清晰。
■ 遵循设计原则。
■ 封装性非常好。
状态模式的缺点
■ 子类太多,不易管理。
2. 状态模式的效果
■ 状态模式需要对每一个系统可能取得的状态创建一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。所有与一个特定的状态有关的行为都被包装到一个特定的对象里面,使得行为的定义局域化。因为同样的原因,如果有新的状态以及它对应的行为需要定义时,可以很方便地通过设立新的子类的方式加到系统里,不需要改动其他的类。
■ 由于每一个状态都被包装到了类里面,就可以不必采用过程性的处理方式,使用长篇累牍的条件转移语句。
■ 使用状态模式使系统状态的变化变得很明显。由于不用一些属性来指明系统所处的状态,因此,就不用担心修改这些属性不当而造成的错误。
■ 可以在系统的不同部分使用相同的一些状态类的对象。这种共享对象的方法是与享元模式相符合的,事实上,此时这些状态对象基本上是只有行为而没有内部状态的享元。
■ 状态模式会造成大量的小状态类,但是可以使程序免于大量的条件转移语句,使程序实际上更易于维护。
■ 系统所选的状态子类均是从一个抽象状态类或接口继承而来,Java 语言的特性使得在Java语言中使用状态模式较为安全,多态性原则是状态模式的核心。
3. 状态模式的使用场景
■ 对象的行为依赖于它所处的状态,即行为随状态改变而改变的场景。
■ 对象在某个方法里依赖于一重或多重条件分支语句,此时可以使用状态模式将分支语句中的每一个分支都包装到一个单独的类中,使得这些条件分支语句能够以类的方式独立存在和演化。如此,维护这些独立的类就不再影响到系统的其他部分。
package com.ypf;
import lombok.AllArgsConstructor;
import lombok.Data;
public class Main {
public static void main(String[] args) {
Person person = new Person("ypf", new PersonHappyState());
person.smail();
person.cry();
person.say();
System.out.println("--------改变状态----------");
person.setState(new PersonSadState());
person.smail();
person.cry();
person.say();
}
}
//环境(Context)角色
@Data
@AllArgsConstructor
class Person {
private String name;
private PersonState state;
public void smail() {
state.smail();
}
public void cry() {
state.cry();
}
public void say() {
state.say();
}
}
//抽象状态(State)角色
abstract class PersonState {
abstract void smail();
abstract void cry();
abstract void say();
}
//具体状态(Concrete State)角色
class PersonHappyState extends PersonState {
@Override
void smail() {
System.out.println("PersonHappyState...smail");
}
@Override
void cry() {
System.out.println("PersonHappyState...cry");
}
@Override
void say() {
System.out.println("PersonHappyState...say");
}
}
//具体状态(Concrete State)角色
class PersonSadState extends PersonState {
@Override
void smail() {
System.out.println("PersonSadState...smail");
}
@Override
void cry() {
System.out.println("PersonSadState...cry");
}
@Override
void say() {
System.out.println("PersonSadState...say");
}
}
//具体状态(Concrete State)角色
class PersonNervousState extends PersonState {
@Override
void smail() {
System.out.println("PersonNervousState...smail");
}
@Override
void cry() {
System.out.println("PersonNervousState...cry");
}
@Override
void say() {
System.out.println("PersonNervousState...say");
}
}