一、设计模式原则
(1)单一职责原则,就是一个类只负责做一件事情。这样就可以做到解耦合的效果,让代码看起来比较清爽,也体现了java的封装性。还有个原则叫迪米特法则,就是一个对象对另一个对象有尽量少的了解,说的也是解耦合的事情。
(2)里氏替换原则和依赖导致原则,说的是继承的事情。父类可以做的事情,子类都可以去做,子类可以尽量去依赖父类去做事情,但是反过来,父类不能依赖子类去做一些事情。体现了java的继承特性。
(3)接口隔离原则,接口也应该尽可能的隔离开来。其实类写多了,的确耦合性低,为了让他们交流起来,用的最多的就是接口,毕竟只需要知道做什么,怎么做,去访问那个具体的类吧。
(4)开闭原则,对修改关闭,对拓展开放。就是代码需要有很好的延展性,对原有代码结构不能破坏。
二、创建者模式
创建者模式就是为了用优雅的方式创建我们使用的类。
1. 简单工厂模式
这个用的比较少,就是有个工厂,告诉你我要什么东西,你造好了给我就行。
public interface Ball {
public String create();
}
public class Basketball implements Ball {
@Override
public String create() {
return "give you a basketball";
}
}
public class Soccer implements Ball {
@Override
public String create() {
return "give you a soccer";
}
}
public class EasyBallFactory {
public static Ball createBall(String name) {
if ("basketball".equals(name)) {
return new Basketball();
} else if ("soccer".equals(name)) {
return new Soccer();
} else {
return null;
}
}
public static void main(String[] args) {
Ball basketball = EasyBallFactory.createBall("basketball");
Ball soccer = EasyBallFactory.createBall("soccer");
System.out.println(basketball.create()); // give you a basketball
System.out.println(soccer.create()); // give you a soccer
}
}
2. 工厂模式
这个其实和简单工厂模式差不太多,就是将工厂继续拆分,比如说刚刚EasyBallFactory是一个总工厂,我们现在拆分成BasketballFactory和SoccerFactory分别生产篮球和足球。某个工厂内部可以根据需求生产不同的产品,比如说SoccerFactory可以生产不同大小的出来。
public interface BallFactory {
public Ball create();
}
public class BasketballFactory implements BallFactory {
@Override
public Ball create() {
// do something
return null;
}
}
public class SoccerFactory implements BallFactory {
@Override
public Ball create() {
// do something
return null;
}
}
3. 抽象工厂模式
抽象工厂模式主要设计产品组的概念,就是某一个工厂生产出配套的一系列产品。例如,在生产足球的同时,SoccerFactory还可以生产与之配套的足球杂志。
public interface Journal {
public String create();
}
public class SoccerJournal implements Journal {
@Override
public String create() {
return "give you a Soccer Journal";
}
}
public class SoccerFactory implements BallFactory {
@Override
public Ball create() {
return new Soccer();
}
public Journal createJournal() {
return new SoccerJournal();
}
}
4. 单例模式
单例模式有很多种形式,优秀实践应该是两重判断,保证只new出来一个。单例可以说是非常普遍的设计模式了。单例就是指在服务容器的生命周期中只能有这么一个。比如说Servlet、Spring中注入的Bean等等都是单例的。
public class ShiroUtils {
public static Session session;
public static Session getSession() {
if (null == session) {
synchronized (ShiroUtils.class) {
if (null == session) {
session = SecurityUtils.getSubject.getSession();
}
}
}
return session;
}
}
5. 建造者模式
将一个复杂对象分布创建。如果一个超大的类的属性特别多,我们可以把属性分门别类,不同属性组成一个稍微小一点的类,再把好几个稍微小点的类窜起来。比方说一个电脑,可以分成不同的稍微小点的部分CPU、主板、显示器。CPU、主板、显示器分别有更多的组件,不再细分。
@Data
public class Computer {
private CPU cpu; // cpu是个接口,有不同实现如InterCPU、AMDCPU等等
private MainBoard mainBoard; // mainBoard是个接口,有不同的实现
private DisPlayer disPlayer; // disPlayer是个接口,有不同的实现
}
public abstract class Builder {
abstract void buildCPU();
abstract void buildMainBoard();
abstract void buildDisPlayer();
abstract Computer createComputer();
}
public class AppleBuilder extends Builder {
private Computer computer = new Computer();
@Override
void buildCPU() {
computer.setCpu(new InterCPU());
}
@Override
void buildMainBoard() {
computer.setMainBoard(new AMainBoard());
}
@Override
void buildDisPlayer() {
computer.setDisPlayer(new ADisPlayer());
}
@Override
Computer createComputer() {
return computer;
}
}
SpringBoot实现了0配置,几乎所有的配置都写到了java代码中,大量的配置不得不让配置类采用建造者模式,这样层次比较清晰。
6. 原型模式
原型模式用的比较少,用于创建重复对象。需要实现Cloneable,可以选择重写clone()方法。clone分为浅克隆和深克隆。浅克隆只是克隆引用,对象还是一个。深克隆是对象也新创建了一个。
@Data
@Builder
public class User implements Cloneable {
private String name;
private int age;
@Override
protected User clone() throws CloneNotSupportedException {
return new User(this.name, this.age);
}
public static void main(String[] args) throws CloneNotSupportedException {
User user1 = new User("HelloWorld", 18);
User user2 = user1.clone();
user1.setAge(25);
System.out.println(user2.getAge()); // 18
}
}
三、结构型模式
上面的设计模式可以帮助我们非常优雅的创建出来对象,下面看几个对象关系之间的模型。
7. 代理模式
Spring的AOP用的是动态代理,何为动态代理?用过Spring的小伙伴都知道吧。代理就是,一个对象辅助另一个对象去做某件事,同时还可以增加一点辅助功能。例如,你买车,的确是你花钱把车买到了,但是你不可能直接去和厂家谈吧,你应该通过4S店购买,同时4S店帮助你入保险扣税等操作,最终你才得到了你想要的车。
public interface Buy {
public void buyCar();
}
public class People implements Buy {
@Override
public void buyCar() {
System.out.println("you get a car");
}
}
public class ProxyPeople implements Buy {
private People people;
public ProxyPeople(People people) {
this.people = people;
}
@Override
public void buyCar() {
System.out.println("4s店帮你纳税、上保险 ...");
people.buyCar();
}
public static void main(String[] args) {
Buy buy = new ProxyPeople(new People());
buy.buyCar(); // 4s店帮你纳税、上保险 ... you get a car
}
}
8. 适配器模式
适配器,顾名思义,是让两个不兼容的东西可以一起工作。例如插座的电源是220V,手机直接给他220V 50HZ的交流电我相信一般都会直接炸了(除了诺基亚...)手机充电器就进行了适配,将电压变小,交流电变成直流电。除了这种需要改变属性的操作,适配器还用于在接口继承方面。假设一个***接口有一大堆方法需要实现类实现,我新写了个类只是想选择的实现一两个接口,那其他的方法我是不是都需要实现一下,即使是空实现(单纯实现,不进行任何逻辑操作),这时我们就需要一个适配器类,空实现那些方法,我的新类只需要继承这个适配器类就好了,要是想实现某个方法,只需要重写掉配置类中对应的方法就好。这种模式基本都会用到,毕竟谁的代码还没个***接口啊。
public interface ATopIntf {
public void one();
public void two();
public void three();
}
public class Adapter implements ATopIntf {
@Override
public void one() {
}
@Override
public void two() {
}
@Override
public void three() {
}
}
public class You extends Adapter {
@Override
public void one() {
super.one();
System.out.println("one");
}
}
9. 桥接模式
就是用于抽象化和实现化的解耦。提高了代码的拓展性,并且可以实现代码的动态切换。 最开始的Ball、Basketball、Soccer接着用,增加新的类。
public class BallCut {
private Ball ball;
public Ball getBall() {
return ball;
}
public void setBall(Ball ball) {
this.ball = ball;
}
public void create() {
System.out.println(ball.create());
}
public static void main(String[] args) {
BallCut ballCut = new BallCut();
ballCut.setBall(new Basketball());
ballCut.create(); // give you a basketball
ballCut.setBall(new Soccer());
ballCut.create(); // give you a soccer
}
}
10. 装饰模式
一个装饰类,在原来类的基础上增加一点功能。是不是和代理模式很像,我甚至可以将整个代码搬过来照样可以说的通。这两个模式意思上有点差别,代理模式是原对象做不了那件事,必须让代理对象去做,主导侧重于代理对象,比如说买车。装饰模式是说,就是让原对象直接去做这件事,只是功能上增强一点,主导在于原对象,比如说炒菜的时候撒点盐。
11. 外观模式
又称门面模式,就是一个门面,一个操作无需让对象知道其内部实现的复杂度,尽量让用户感知到是非常简单的。这就是为什么我们controller层尽量(或者说一定)少些业务逻辑,让controller层只是起到一个传参和通用性参数校验的功能,剩下的全交给service去做吧。我们还需要在代码中不断将“长得”特别长的代码封装成一个方法,“让处处都有好看的外观”。看一下我们曾写过的代码,这里只起到了传参的作用,究竟这个足球是怎么创建出来的,客户端不必担心。
public static void main(String[] args) {
Ball soccer = EasyBallFactory.createBall("soccer");
System.out.println(soccer.create()); // give you a soccer
}
12. 组合模式
组合模式是将存在某种包含关系的数据组织在一起,典型的例子就是树状结构。例如菜单功能,一个菜单除了自己该有的属性,还可能包含子菜单,创建的时候可以使用递归的方法。
@Data
public class Menu {
private String name;
private int type;
private List<Menu> childMenus;
}
13. 享元模式
享元模式就是尽可能的让用户复用已经有的对象,从而避免造成反复创建对象的资源浪费。首先就会想到数据库连接池还有String常量池,延伸一下,几乎所有和缓存有关的代码,多少都会用到享元模式。享元模式要求大部分的对象可以外部化。这边要说两个概念,享元模式对象的属性可以分为两个部分,内部状态和外部状态,内部状态是指不会随环境而改变的值,比如说个人信息,外部状态是指随环境改变的值,不能进行共享的信息,如某大学生选修的课程。
public abstract class Flyweight {
// 内部状态
private String name;
private String age;
// 外部状态
private final String subject;
protected Flyweight(String subject) {
this.subject = subject;
}
// 行为
public abstract void exam();
public String getSubject() {
return subject;
}
}
public class RealFlyweight extends Flyweight {
@Override
public void exam() {
System.out.println(this.getSubject() + "is examing ...");
}
public RealFlyweight(String subject) {
super(subject);
}
}
public class FlyweightFactory {
// 定义一个池子
private static HashMap<String, Flyweight> pool = new HashMap<>();
public static Flyweight getFlyweight(String subject) {
Flyweight flyweight = null;
if (pool.containsKey(subject)) {
flyweight = pool.get(subject);
} else {
flyweight = new RealFlyweight(subject);
pool.put(subject, flyweight);
}
return flyweight;
}
public static void main(String[] args) {
System.out.println(pool.size()); // 0
getFlyweight("math");
System.out.println(pool.size()); // 1
getFlyweight("english");
System.out.println(pool.size()); // 2
getFlyweight("math");
System.out.println(pool.size()); // 2
}
}
四、行为型模式
创建了对象,对象之间有了结构关系,就要看下怎么更加优雅的相互作用了。
14. 策略模式
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。可以说是一组算法的封装,根据客户端给出的不同要求,进行不同的运算。比如下面这个简易计算器。
public interface Strategy {
public int doOperation(int num1, int num2);
}
public class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubstract implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
}
}
15. 观察者模式
定义了一种一对多的依赖关系,当一个对象(被观察者)状态改变的时候,所有依赖于该对象的观察者都会被通知,从而进行相关操作。很多中间件都依赖于观察者模式,例如RabbitMQ,还有那些事件驱动模型(好像node就是)。下面举个例子,被观察者是监考老师,考试时间结束,通知所有观察者学生上交试卷。
@Data
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public void doSomething() {
System.out.println(getName() + "交卷了 ...");
}
}
public class Teacher {
private Set<Student> students = new HashSet<>();
public void addStudent(Student student) {
students.add(student);
}
public void removeStudent(Student student) {
students.remove(student);
}
public void doNotify() {
for (Student student : students) {
student.doSomething();
}
}
public static void main(String[] args) {
Teacher teacher = new Teacher();
Student student1 = new Student("张三");
Student student2 = new Student("李四");
teacher.addStudent(student1);
teacher.addStudent(student2);
teacher.doNotify(); //张三交卷了 ... 李四交卷了 ...
}
}
16. 责任链模式
责任链模式为请求创建一个接收者对象的链,对发送者和接受者进行解耦合。filter链就是责任链模式。
public class Handler {
// 下一个处理者
private Handler nextHandler;
public final Response handleMessage(Request request) {
Response response = null;
if (this.getHandlerLevel().equals(request.getRequestLevel())) {
response = this.echo(request);
} else {
if (this.nextHandler != null) {
// 传递给下一个
response = this.nextHandler.handleMessage(request);
} else {
response = new Response();
}
}
return response;
}
public Handler getNextHandler() {
return nextHandler;
}
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
protected abstract Level getHandlerLevel();
protected abstract Response echo(Request request);
}
17. 模板方式模式
一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。SpringBoot为用户封装了很多继承代码,都用到了模板方式,例如那一堆XXXtemplate。
public abstract class DBTemplate {
abstract void open();
abstract void select();
abstract void close();
// 一个搜索模板
public final void selectTemplate() {
open();
select();
close();
}
}
public class MysqlDB extends DBTemplate {
@Override
void open() {
System.out.println("Mysql open ...");
}
@Override
void select() {
System.out.println("Mysql select ...");
}
@Override
void close() {
System.out.println("Mysql close ...");
}
public static void main(String[] args) {
DBTemplate mysql = new MysqlDB();
mysql.selectTemplate(); // Mysql open ... Mysql select ... Mysql close ...
}
}
18.状态模式
简单来说,就是一个对象有不同的状态,根据状态不同,可能有不同的行为。
public interface State {
public void doAction(Context context);
}
public class StartState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString() {
return "Start State";
}
}
public class StopState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString() {
return "Stop State";
}
}
public class Context {
private State state;
public Context() {
state = null;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context); // Player is in start state
System.out.println(context.getState().toString()); // Start State
StopState stopState = new StopState();
stopState.doAction(context); // Player is in stop state
System.out.println(context.getState().toString()); // Stop State
}
}
19. 迭代器模式
提供一个方法,可以顺序访问一个对象内部的各个元素,不需要知道内部构造。现在基本很少自己实现迭代器了,基本成熟的框架或者强大的JDK都会给出访问的方法,比如说java中iterator。这样做主要是进一步封装对象内部的结构,让行为和结构相耦合。这个不举例子了,用过iterator这个的小伙伴应该都清楚,就是不停的next,去访问下一个元素。
20. 命令模式
命令模式是将请求以命令的形式包裹在对象中,并传递给对象,调用对象寻找到处理该命令的合适的对象,并将该命令传递给相应的对象,该对象执行。简单点说就是不同请求都封装成一个对象,不同的请求调用不同的执行者。
// 真正干活的对象
public class Receiver {
public void doSomething() {
System.out.println("Receiver干活");
}
}
public abstract class Command {
public abstract void exectue();
}
public class ConcreteCommand extends Command {
// 干活那个
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void exectue() {
this.receiver.doSomething();
}
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
command.exectue(); // Receiver干活
}
}
21. 备忘录模式
相当于做一个快照,在不破坏对象本身结构的情况下,记录对象的一个状态,合适的时候可以恢复到这种状态。数据库做事务回滚的时候就用了这种方式。这里需要注意的是,对象不与备忘录本身耦合,而是跟备忘录管理类耦合(就是List<备忘录>),这个好理解,毕竟快照不止一个嘛。
// 备忘录
public class Memento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState() {
return state;
}
}
// 某对象
public class Originator {
private String state;
public Memento saveStateToMemnto() {
return new Memento(state);
}
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
// 备忘录管理类
public class CareTaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento memento) {
mementoList.add(memento);
}
public Memento get(int index) {
return mementoList.get(index);
}
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemnto());
originator.setState("State #3");
careTaker.add(originator.saveStateToMemnto());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState()); // Current State: State #4
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState()); // First saved State: State #2
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState()); // Second saved State: State #3
}
}
22. 访问者模式
当对特定角色进行访问的时候,需要通过访问者进行访问。一个对象不太方便被你直接访问的时候,你需要将自己的引用交给访问者,通过访问者去访问该对象。比如说,化学课,想看一个细胞结构,由于肉眼无法直接看到微观世界的玩意,需要通过显微镜间接访问。
23. 中介者模式
降低对象或者说事物之间通讯的复杂性,降低耦合。比如说分布式系统中,不是需要实时反馈的操作,我们无需直接对接,只需将想做的事告诉中间件,中间件告诉另外一个系统。比如说,访问(用户点击)一条新闻操作,同时需要记录是谁访问了什么新闻,同时给新闻浏览次数加1,还要实时更新用户喜好...总之要更新n个数据库表,甚至还要操作像ES,Mongo等多个中间件数据。但是对于用户来说,我只是做了一个点击操作,希望得到的结果就是看条新闻啊,你这么多操作,搞这么慢,用户体验很差啊,而且并发量也很低,那不如做成两个小系统,A系统,拉取新闻,推送,并组装一个信息扔给MQ中间件,ok,结束,用户看到新闻。然后B系统监听,得到这个消息,进行各种更新,这里,这个中间件就是我们的中介。再比如说,MVC中的控制层就是展示层和模型层的中介。再比如说,下面这个聊天室:
public class ChatRoom {
public static void showMessage(User user, String message) {
System.out.println(new Date().toString() + " [" + user.getName() + "] : " + message);
}
}
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void sendMessage(String message) {
ChatRoom.showMessage(this, message);
}
public static void main(String[] args) {
User robert = new User("Robert");
User john = new User("John");
robert.sendMessage("Hello! John!"); // Wed Aug 26 19:19:41 CST 2020 [Robert] : Hello! John!
john.sendMessage("Hello! Robert!"); // Wed Aug 26 19:19:41 CST 2020 [John] : Hello! Robert!
}
}
24. 解释器模式
构建一种翻译方式,将某种语言或描述翻译成我们很好理解的语言或者描述。这里很好理解的意思是看得懂,看的快。本来我也想举什么编译器这种高大上的,将底层语言甚至机械语言和我们使用的高级编程语言。后来想了想,其实Map就可以看作一个很好的编译器,key你可以存放一个非常小的字符串,value理论上你可以存放任何东西,所以代码就不写了。