目录
软件设计七大原则
- 开闭原则:开放扩展、关闭修改。抽象出接口,可实现接口、继承类进行扩展,但尽量不要修改接口。
- 依赖倒置原则:高层某块不应该依赖低层模块的具体实现,而是依赖低层模块的接口,将耦合度降低到接口层次
- 单一职责原则:一个类、接口、方法只负责一项功能,降低类的复杂度、变更引起的风险,提高类的可读性,易于维护
- 接口隔离原则:接口之间相互独立,尽量通过接口来访问
- 迪米特原则:又叫做最少知道原则,使用其它模块时只关心暴露出来的接口,不关系内部的具体实现
- 里氏替换原则:父类对象都可以替换为子类对象,即父类适用的地方,子类也应该适用
- 组合复用原则:尽量用包含来代替继承实现复用,eg. controller要使用某个service,尽量写成成员变量包含进来,而不是继承service
只是参考,根据需求使用
设计模式简介
标准的设计模式有23种,简单工厂不属于标准设计模式,但用得也多。
设计模式可分为3类
- 创建型:用于设计对象的创建方式
- 结构型:用于设计类的结构、类之间的结构
- 行为型:用于设计类的行为
创建型(1+5种)
简单工厂(不属于标准的设计模式)
一个工厂创建多个类的实例
//写成实例方法、静态方法均可
public class UserFactory{
public Student getStudent(){
return new Student();
}
public Teacher getTeacher(){
return new Teacher();
}
}
优点:简单;缺点:与多个类耦合在一起,不好扩展
工厂都具有的优点:通过工厂创建对象,无需了解具体创建过程
工厂方法
一个工厂只创建一个类的实例,使用各自的工厂创建各自的实例
//写成接口、抽象类均可,不要也行
public interface UserFactory{
User getUser();
}
public class StudentFactory implements UserFactory{
@Override
public User getUser() {
return new Student();
}
}
public class TeacherFactory implements UserFactory{
@Override
public User getUser() {
return new Teacher();
}
}
优点:扩展性好;缺点:需要写很多工厂类
抽象工厂
顾名思义,对工厂进行抽象,常用于多个对象的组装
//对工厂的抽象
public interface ComputorFactory{
//多个对象
Cpu getCpu(String cpu);
Memory getMemory(String memory);
Disk getDisk(String disk);
}
public class MacComputorFactory implements ComputorFactory{
@Override
public Cpu getCpu() {
return new Cpu("i5");
}
@Override
public Memory getMemory() {
return new Memory("ddr3 8g");
}
@Override
public Disk getDisk() {
return new Disk("ssd 256g");
}
}
public class MacProComputorFactory implements ComputorFactory{
@Override
public Cpu getCpu() {
return new Cpu("i7");
}
@Override
public Memory getMemory() {
return new Memory("ddr4 16g");
}
@Override
public Disk getDisk() {
return new Disk("ssd 500g");
}
}
优点:可组装多个不同类型的对象;缺点:在工厂实现中可能有较多的重复代码
建造者模式
通过建造者来建造对象的各部分成员,常用于建造成员变量本身比较复杂的对象
@Getter
@Setter
public class Computor {
private String cpu;
private String memory;
private String disk;
}
//建造者
//写成接口、抽象类均可,不要也行
public interface ComputerBuilder{
void buildCpu(String cpu);
void buildMemory(String memory);
void buildDisk(String disk);
Commputor getComputor();
}
public class ComputerActualBuilder implements ComputerBuilder {
private Computer computer = new Computor(); //要建造的对象,空参构造器
@Override
public void buildCpu(String cpu) {
computor.setCpu(cpu); //实际建造时不会这么简单,往往是很复杂的操作
}
@Override
public void buildMemory(String memory) {
computor.setMemory(memory);
}
@Override
public void buildDisk(String disk) {
computor.setDisk(disk);
}
@Override
public void getComputor() {
return computor;
}
}
//生产者
@Setter //提供setter方法注入建造者
class ComputerProducer {
private ComputerBuilder computerBuilder; //建造者
public Computor buildComputer(String cpu, String memory, String disk){
computerBuilder.buildCpu(cpu); //开始建造
computerBuilder.buildMemory(memory);
computerBuilder.buildDisk(disk);
return computerBuilder.getComputor(); //返回建造好的对象
}
}
//使用
ComputerBuilder computerBuilder = new ComputerActualBuilder(); //建造者
ComputerProducer computerProducer = new ComputerProducer(); //生产者
computerAssembler.setComputerBuilder(computerBuilder); //注入建造者
Computer computer = computerProducer.buildComputor("i7", "ddr4 16g", "ssd 1t"); //生产对象
优点:可建造成员变量本身比较复杂的对象;缺点:编码量大、偏复杂
单例模式
常用于构建资源占用多、创建时间开销大的大对象,创建时缓存对象实例,保证该类全局最多只有一个实例,获取到的都是同一个实例,常见的应用比如 线程池、缓存容器、注册表、日志对象等。
优点:减少资源占用,提高获取实例的速度。
缺点:使用private 隐藏了构造器,扩展性差;多线程并发访问单例实例时,可能存在线程安全问题。
创建单例的2种模式
- 饿汉式:在类加载时就实例化,本身线程安全
- 懒汉式:延迟加载,在需要使用实例时才创建实例,多线程环境下可能创建多个实例,需要自行实现单例创建的线程安全
饿汉式
public class A {
//static,类加载时就初始化实例
private static A a = new A();
//..... //其它成员
//private隐藏构造器
private A(){
}
//把获取实例的方法暴露出去。只有1步,具有原子性
public static A getInstance(){
return a;
}
//.... //其它方法
}
懒汉式写法一:线程不安全
//懒汉式 写法一
class A {
private static A a;
//.....
private A(){
}
//多步,不具有原子性,不是线程安全的
public static A getInstance(){
if (null == a){
a = new A();
}
return a;
}
//.....
}
懒汉式写法二:双重校验+加锁,线程安全
class A {
//volatile禁止指令重排序
private static volatile A a;
private A() {
}
//多步,不具有原子性,可能发生并发问题,需要加锁
public static A getInstance() {
//双重校验(DCL)
if (null == a) {
//加锁
synchronized (A.class) {
if (null == a) {
a = new A();
}
}
}
return a;
}
}
- 直接用synchronized修饰方法,或直接给外层if加锁,这2种方式性能都比较低,只给a=new A()加锁即可。
- 最初创建实例时可能有多个线程通过了外层if校验,等待获取类锁,从而出现多次创建实例的问题,一般在创建实例时还需要校验一次,使用双重校验(double check lock)保证只创建一个实例。
说明
不管是饿汉式还是懒汉式,反序列化时jvm默认会使用反射创建一个新的对象,这会破坏单例对象的唯一性,所以单例一般不允许序列化。何况为了保证单例本身的线程安全,很多单例类都不使用表示状态的成员字段,没必要序列化。
原型模式
创建实例时直接克隆已存在的实例,减少重新创建实例的时间开销
//Object有clone()方法,但不能直接使用,需要实现Cloneable,重写里面的clone()方法才能使用
public class Student implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException { //返回类型是Object
return super.clone(); //调用Object的clone()
}
}
Student student = new Student();
try {
Student student1 = (Student) student.clone(); //通过克隆来创建实例
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
clone()直接复制二进制流,不调用构造器创建实例。
clone()是浅拷贝,如果有引用型的成员变量,要额外做一些操作实现深拷贝
public class Student implements Cloneable {
private Teacher teacher; //引用型成员变量。注意:Teacher类也要实现Cloneable接口,并重写clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone(); //克隆整个对象
student.teacher = student.teacher.clone(); //克隆引用型成员,指向新克隆出来的
return student;
}
}
优点:减少了重新创建实例的时间开销;缺点:有引用型成员变量时可能会写成浅拷贝,从而引发一些问题
结构型(7种)
外观模式(门面模式)
向外部提供统一的接口来访问,外部不直接访问具体的业务方法
eg. controller -> service -> dao,将controller作为接口暴露给外部调用,外部不直接访问处理业务的service、dao
优点:无需了解内部具体实现,方便模块间调用;缺点:修改业务方法时,可能会影响暴露出来的接口,维护难度增加
装饰者模式
在保留原有功能的基础上进行扩展
//原类
@Getter
public class Computor {
private String cpu="i7";
private String memory="8g";
private String disk="500g ssd";
private double sum = 6000; //总价
}
//装饰者。可以使用多个装饰者添加不同的功能
public class ComputorDecorator {
private Computor computor; //要被装饰的对象,写成成员变量的形式
private double memoryPrice = 200; //8g内存单价
private double diskPrice = 500; //1t机械硬盘单价
private int memoryCount; //加装内存条数量
private int diskCount; //加装机械硬盘数量,0表示不加装,1表示加装1个
//注入要被装饰的对象
public ComputorDecorator(Computor computor){
this.computor = computor;
}
//加装一条8g内存
public void addMemory() {
if (this.memoryCount==0)
this.memoryCount = 1;
else
System.out.println("只能加装1条");
}
//加装1t机械硬盘
public void addDisk() {
if (this.diskCount==0)
this.diskCount = 1;
else
System.out.println("只能加装1个机械硬盘");
}
public double sum() { //总价
return computor.getSum() + memoryPrice * memoryPrice + diskCount * diskPrice; //在原价的基础上加
}
}
//用装饰者代替原来的对象来使用
Computor computor = new Computor();
ComputorDecorator computorDecorator = new ComputorDecorator(computor);
computorDecorator.addMemory(); //加装一条8g内存
Double sum = computorDecorator.sum();
优点:继承可以扩展类,但破坏了原类的封装性,装饰者模式提供了一种新的类扩展方式,不会破坏原类的封装
适配器模式
把接口、类转换为期待的接口、类,使原本不兼容的接口、类可以协同工作。
享元模式
对于频繁使用的大对象,每次都重新创建会很花资源、时间,可以预先创建好一些对象放到集合中,使用时直接从集合中取,用完重置为初始值放回到集合中。
享元模式的常见应用比如线程池、数据库连接池。
实现Cloneable接口并重写clone()方法
class B implements Cloneable{
@Override
protected B clone() throws CloneNotSupportedException {
return (B) super.clone();
}
}
@Getter
@Setter
public class A implements Cloneable{
private int id;
private B b;
@Override
public A clone() throws CloneNotSupportedException {
A target = (A)super.clone();
target.setB(this.getB().clone()); //对引用型成员变量b使用深拷贝
return target;
}
}
默认的clone()是浅克隆,如果有引用类型的成员变量,需要自行实现深拷贝,且该引用型成员变量也要是可克隆的(实现Cloneable接口并重写clone()方法)。
Integer、Float等包装类型也算是引用型。
组合模式
组合模式用于将多个对象(节点)组合成树形结构,常用于菜单、文件系统、家谱等
//成员
public class Member{
private String name; //姓名
private String gender; //性别
public Member(String name, String gender) {
this.name = name;
this.gender = gender;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
//可能要改名
public void setName(String name) {
this.name = name;
}
}
//男性成员,记录老婆、孩子
public class MaleMember extends Member{
private String wife; //老婆
private ArrayList<Member> children = new ArrayList<>(); //孩子
public MaleMember(String name, String gender) {
super(name, gender);
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
//添加一个孩子
public void addChild(Member member){
children.add(member);
}
//获取所有孩子
public ArrayList<Member> getChildren() {
return children;
}
}
//女性成员,要出嫁,只记录丈夫,不记录其子女
public class FemaleMember extends Member{
private String husband; //丈夫
public FemaleMember(String name, String gender) {
super(name, gender);
}
public String getHusband() {
return husband;
}
public void setHusband(String husband) {
this.husband = husband;
}
}
//使用
MaleMember ly = new MaleMember("李渊","男");
ly.setWife("窦皇后"); //李渊娶老婆
MaleMember lsm = new MaleMember("李世民","男"); //李世民出生
ly.addChild(lsm);
lsm.setWife("长孙皇后"); //李世民娶老婆
桥接模式
①抽象与具体实现分离,②使用组合代替继承建立类之间的联系
interface UserDao{
void addUser(User user);
void deleteUser(int id);
User findUserById(int id);
void updateUser(User user);
}
//抽象(接口、抽象类)与具体实现分离
@Repository
class UserDaoImpl implements UserDao{
@Override
public void addUser(User user) {
}
@Override
public void deleteUser(int id) {
}
@Override
public User findUserById(int id) {
return null;
}
@Override
public void updateUser(User user) {
}
}
interface UserService {
void addUser(User user);
void deleteUser(int id);
User findUserById(int id);
void updateUser(User user);
}
//抽象(接口、抽象类)与具体实现分离
@Service
class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao; //使用组合代替继承建立类之间的联系
@Override
public void addUser(User user) {
userDao.addUser(user);
}
@Override
public void deleteUser(int id) {
userDao.deleteUser(id);
}
@Override
public User findUserById(int id) {
return userDao.findUserById(id);
}
@Override
public void updateUser(User user) {
userDao.updateUser(user);
}
}
抽象(接口、抽象类)、实现分离,组合时使用抽象,将耦合降低到抽象层次
代理模式
对原类进行增强,用代理代替原类进行工作,常用于aop
参考:https://blog.csdn.net/chy_18883701161/article/details/106509363
行为型(11种)
模板方法
将业务划分为多个步骤(方法),在模板方法中调用这些方法,组成完整的业务处理流程。
常用于流程相同、相似的业务处理,可重写模板方法提供不同的处理方式
//招聘
public abstract class Recruit{
//笔试
public boolean writtenTest(){
}
//面试
public boolean interview(){
}
//发放offer
public void issueOffer(){
}
//模板方法,招聘,在模板方法中调用其它方法,组成完整的业务流程
public abstract void recruit();
}
//通用招聘,笔试->面试->发放offer
public class CommonRecruit extends Recruit{
//模板方法
@Override
public void recruit(){
// 先进行笔试
if (this.writtenTest()) {
// 通过则进行面试
if (this.interview()){
// 通过则发放 offer
issueOffer();
}
}
}
}
//内推免笔试,面试->发放offer
class RecommendRecruit extends Recruit{
//模板方法
@Override
public void recruit(){
//面试
if (this.interview()==true){
//通过则发放offer
issueOffer();
}
}
}
迭代器模式
用单独的类作为迭代器Iterator来遍历集合,将数据存储、遍历分离,常见的比如jdk自带的集合的迭代器
迭代器一般要有2个方法
- hasNext() 判断是否是最后一个元素(是否还有下一个元素)
- next() 获取下一个元素
策略模式
封装处理同一业务的多种策略,根据需求切换不同的策略即可,不需要了解策略的具体实现
常见的比如spring的7种事务传播策略、数据库的4种隔离级别、线程池的4种拒绝策略、促销方案、出行线路选择(最短时间、最低花费、最少换乘)等
//促销策略接口
public interface SalePromotionStrategy {
//传入总金额,返回折算后总金额
double doPromotion(double sum);
}
//策略一:满100减20
class Strategy1 implements SalePromotionStrategy{
@Override
public double doPromotion(double sum) {
if (sum >= 100){
sum -= 20;
}else{
System.out.println("亲,尚不满足满减条件");
}
return sum;
}
}
//策略二:满300减100
class Strategy2 implements SalePromotionStrategy {
@Override
public double doPromotion(double sum) {
if (sum >= 300){
sum -= 100;
}else{
System.out.println("亲,您的购买金额尚未达到300元呢");
}
return sum;
}
}
//策略三:满1000减500
class Strategy3 implements SalePromotionStrategy {
@Override
public double doPromotion(double sum) {
if (sum >= 1000){
sum -= 500;
}else{
System.out.println("亲,您的购物金额尚未达到1000元呢");
}
return sum;
}
}
//使用时,把策略接口写成成员变量,提供setter方法用于注入、修改策略
//直接使用策略即可。无需关心策略的具体实现,修改策略时无需修改其它部分
private SalePromotionStrategy strategy;
public void setSalePromotionStrategy(SalePromotionStrategy strategy) {
this.strategy = strategy;
}
//结算时使用指定策略计算出优惠后的应付金额
double paySum = this.strategy.doPromotion(sum);
解释器模式
将语句按照自定义的方式解释执行
eg. 可以自定义字符串、数字之间的*解释执行规则是:拼接n个相同的字符串, “hello”*2 => “hellohello”
解释器模式基本不用,此处不详细说明
观察者模式
对象可以被其它对象观察,当对象被修改时,自动通知所有订阅了该对象的观察者。
事件驱动模型是观察者模式的典型应用,此处以jdk自带的事件驱动模型为例。
eg. 观察一个集合,集合中新增元素时自动通知观察者
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
/**
* 被观察的对象,需要继承 Observable
* Observable中的方法很多都使用 synchronized 修饰,线程安全
*
* @param <T> list的元素类型
*/
public class MyList<T> extends Observable {
private List<T> list = new LinkedList<>();
/**
* 获取列表
*
* @return 列表
*/
public List<T> getList() {
return this.list;
}
/**
* 添加元素
*
* @param ele 要添加的元素
* @return 操作结果
*/
public boolean add(T ele) {
boolean result = list.add(ele);
if (result) {
//标识状态改变
this.setChanged();
//通知所有订阅了此list的观察者。可根据需要传递数据给观察者,不需要传递数据可以调用无参的notifyObservers()
this.notifyObservers(ele);
}
return result;
}
}
import java.util.Observable;
import java.util.Observer;
/**
* 观察者,需要实现 Observer 接口,重写 update() 方法
*/
public class MyListObserver implements Observer {
/**
* 主题对象发送更新通知时,会自动调用update()方法进行处理
*
* @param o 主题对象(被观察者),会自动传入
* @param arg 主题对象(被观察者)的 notifyObservers() 方法传递的数据
*/
@Override
public void update(Observable o, Object arg) { //被观察的对象、传过来的参数
MyList myLinkedList = (MyList) o;
System.out.println("list:" + myLinkedList.getList() + ", 主题对象传递的数据:" + arg);
}
}
//观察者
MyListObserver myObserver = new MyListObserver();
//主题对象
MyList myLinkedList = new MyList();
//添加观察者。可用多个addObserver()设置多个观察者
myLinkedList.addObserver(myObserver);
//list添加元素,触发更新事件
myLinkedList.add("zhangsan");
myLinkedList.add("lisi");
备忘录模式
保存对象状态,后续可恢复对象至某个状态
备忘录用得不多,一般只用于特定的开发场景,比如
- 开发文本编辑器:Ctrl+Z撤销操作、恢复到上一步
- 开发游戏:进度存档
简单说一下
class Article{
private String title;
private String content;
//.....
}
class ArticleStack{
private Stack<Article> stack = new Stack<>();
//.....
}
push对象到栈中,存储对象不同时期的状态,栈是后进先出的,还原时pop()获取上一次的状态进行还原
命令模式
将操作封装为命令对象,通过命令对象来执行操作
//命令接口
public interface Command{
void execute(); //执行命令
}
//命令
//开放CET4、6的报名通道
public class CETApplyOpen implements Command{
@Override
public void execute() {
//.... //开放cet4、6的报名通道
}
}
//关闭CET4、6的报名通道
public class CETApplyClose implements Command{
@Override
public void execute() {
//.... //关闭cet4、6的报名通道
}
}
//开放CET4、6的查分通道
public class CETQueryOpen implements Command{
@Override
public void execute() {
//.... //开放cet4、6的查分通道
}
}
//关闭CET4、6的查分通道
public class CETQueryClose implements Command{
@Override
public void execute() {
//.... //关闭cet4、6的查分通道
}
}
//执行命令
//把Command作为成员变量,注入要执行的Command对象
private Command command;
//调用execute()执行命令即可
command.execute();
中介者模式
对象之间不直接交互,通过中介者来进行交互
eg. 群聊、聊天室,把群聊、聊天室作为中介者,用户之间通过聊天室进行交流
责任链模式(职责链模式)
一个请求需要多个操作或对象依次处理,形成一条处理链路,常见的比如拦截器栈
//Handler抽象类
public abstract class Handler{
protected Handler handler; //下一个handler。类似链表
public void setHandler(Handler handler) { //设置下一个handler
this.handler = handler;
}
public abstract void handler(); //当前handler的处理
}
//Handler的实现类
class Handler1 extends Handler{
@Override
public void handler() {
//.... //当前handler的处理
if ( this.handler != null){
this.handler.handler(); //调用下一个handler进行处理
}
}
}
class Handler2 extends Handler{
@Override
public void handler() {
//.... //当前handler的处理
if ( this.handler != null){
this.handler.handler(); //调用下一个handler进行处理
}
}
}
//使用多个handler依次处理请求
Handler1 handler1 = new Handler1();
Handler2 handler2 = new Handler2();
handler1.setHandler(handler2); //设置下一个handler
//使用handler1接收请求即可
访问者模式
根据访问者类型,展示相应的内容,通常要校验用户权限
eg. 有些信息只给vip看,有些信息只给管理员看
状态模式
对象存在多种状态,处于不同状态时支持的行为不同。
eg. 订单处于已创建状态时可以付款,处于已付款状态时可以发货,处于已发货状态时可以签收,处于已签收状态时可以进行售后
//订单状态,抽象类
public abstract class OrderStatus{
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
//已创建状态
class CreatedOrderStatus extends OrderStatus{
{
super.setStatus("created");
}
//付款操作
public void pay(){
}
}
//已付款状态
class PaidOrderStatus extends OrderStatus{
{
super.setStatus("paid");
}
//发货操作
public void send(){
}
}
//已发货状态
class SentOrderStatus extends OrderStatus{
{
super.setStatus("sent");
}
//签收操作
public void receive(){
}
}
//已签收状态
class ReceivedOrderStatus extends OrderStatus{
{
super.setStatus("received");
}
//售后操作
public void afterSale(){
}
}
class Order{
private OrderStatus orderStatus = new CreatedOrderStatus(); //使用一个成员来保存状态,状态变化时需要更新此成员
//.....
}