目录
3.5.4 代理、桥接、装饰器、适配器 4 种设计模式的区别
1. 六大设计原则
1.1 单一职责原则
定义: 一个类只负责完成一个职责或者功能。
1.2 开放封闭原则
定义: 对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的
1.3 里氏替换原则
定义: 子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。
1.4 接口分离原则
定义: 要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
1.5 依赖倒置原则
定义: 在设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象
1.6 迪米特法则
定义: 一个类/模块对其他的类/模块有越少的了解越好。简单来说迪米特法则想要表达的思想就是: 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。
2. 创建型设计模式
2.1 概述
创建型设计模式包括:单例模式、工厂模式、建造者模式、原型模式。它主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码
2.2 单例模式
定义: 单例模式用来创建全局唯一的对象。一个类只允许创建一个对象(或者叫实例),那这个类就是一个单例类,这种设计模式就叫作单例模式
2.2.1 饿汉式
饿汉式: 饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
/**
* 单例模式-饿汉式
* 在类加载期间 初始化私有静态实例 保证instance实例创建过程时线程安全的
* 特点:不支持延时加载 获取实例对象的速度较快 但是如果对象较大 而且一直没有使用会造成内存的浪费
*/
public class Student {
//1.私有构造方法
private Student() {
}
//2.本体类中创建私有静态全局对象
private static Student instance=new Student();
//3.提供一个全局访问点 供外部获取单例对象
public static Student getInstance(){
return instance;
}
public static void main(String[] args) {
//测试饿汉式
Student t1 = Student.getInstance();
Student t2 = Student.getInstance();
System.out.println(t1==t2);
}
}
2.2.2 懒汉式
懒汉式: 相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
/**
* 单例模式-懒汉式( 线程不安全)
* 特点:支持延时加载,只有调用getInstance方法时 才会创建对象
*/
public class Teacher {
//1.私有构造方法
private Teacher() {
}
//2在本类中创建私有静态全局对象
private static Teacher instance ;
//3.提供一个全局访问点 供外部获取单例对象
public static Teacher getInstance(){
if(instance==null){
instance=new Teacher();
}
return instance;
}
public static void main(String[] args) {
//测试懒汉式
Teacher t1 = Teacher.getInstance();
Teacher t2 = Teacher.getInstance();
System.out.println(t1==t2);
// for (int i = 0; i < 100000; i++) {
// new Thread(()->{
// Teacher t1 = Teacher.getInstance();
// System.out.println(Thread.currentThread().getName()+"-----"+t1);
//
// }).start();
// }
}
}
2.2.3 双重校验
双重校验: 双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。
/**
* 单例模式-双重校验
*/
public class DoubleCheck {
//1私有构造方法
private DoubleCheck(){
}
//2在本类中创建私有静态全局对象
//使用volatile关键字保证原子性
private static volatile DoubleCheck instance;
//3获取单例对象静态方法
public static DoubleCheck getInstance(){
//第一次判断 如果instance不为null 不进入枪锁 直接返回实例
if(instance==null){
synchronized (DoubleCheck.class){
//第二次判断,抢到锁之后再次进行判断 判断是否为null
if(instance==null){
instance=new DoubleCheck();
/**
* 上面创建对象的代码 在jvm分为3步
* 1分配内存空间
* 2初始化对象
* 3将instance指向分配好的内存空间
* 但是jvm会出现指令重排的情况 导致内存分配好了 然后就将instance指向了分配好的内存空间 ,但是还未完成初始化 因此instance依旧为null
* 所以在后面的判断install==null时 依然可以进入该判断条件
*/
}
}
}
return instance;
}
public static void main(String[] args) {
//双重校验
DoubleCheck t1 = DoubleCheck.getInstance();
DoubleCheck t2 = DoubleCheck.getInstance();
System.out.println(t1==t2);
// for (int i = 0; i < 100000; i++) {
// new Thread(()->{
// DoubleCheck doubleCheck = DoubleCheck.getInstance();
// System.out.println(Thread.currentThread().getName()+"-----"+doubleCheck);
//
// }).start();
// }
}
}
2.2.4 静态内部类
静态内部类: 利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。
/**
* 单例模式-静态内部类 (推荐)
* 根据静态内部类特性-同时解决了 延时加载 线程安全问题 并且代码更加简洁
*/
public class Inner {
private Inner(){
}
//静态内部类
private static class SingletonHandler{
//在静态内部类创建的单例 在装在内部类的时候 再回创建单例对象
private static Inner instance=new Inner();
}
//对外暴露的方法
public static Inner getInstance(){
return SingletonHandler.instance;
}
public static void main(String[] args) {
Inner t1 = Inner.getInstance();
Inner t2 = Inner.getInstance();
System.out.println(t1==t2);
}
}
2.2.5 枚举(推荐方式)
枚举: 最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性(同时阻止了反射和序列化对单例的破坏)。
public enum Singleton{
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Singleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Singleton.INSTANCE.setData(new Teacher("张三"));
Object o1 = Singleton.INSTANCE.getData();
Object o2 = Singleton.INSTANCE.getData();
System.out.println(o1==o2);
}
}
2.3 工厂模式
定义: 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
需求模拟:电商中促销拉新下的业务场景, 新用户注册立即参与抽奖活动 ,奖品的种类有: 打折券, 免费优酷会员,小礼品.
不考虑设计原则,不使用设计模式的方式你可能是这么进行开发的:
实体类
名称 |
描述 |
DiscountInfo |
打折券信息对应实体类 |
YouKuMember |
优酷会员对应实体类 |
SmallGiftInfo |
小礼品信息对应实体类 |
DiscountResult |
打折券操作响应结果封装类 |
AwardInfo |
获奖信息对应实体类 |
实体类定义
public class AwardInfo {
private String uid; //用户唯一ID
private Integer awardType; //奖品类型: 1 打折券 ,2 优酷会员,3 小礼品
private String awardNumber; //奖品编号
Map<String, String> extMap; //额外信息
}
public class DiscountInfo {
//属性信息省略......
}
public class YouKuMember {
//属性信息省略......
}
public class SmallGiftInfo {
private String userName; // 用户姓名
private String userPhone; // 用户手机
private String orderId; // 订单ID
private String relAddress; // 收货地址
}
public class DiscountResult {
private String status; // 状态码
private String message; // 信息
}
服务层
名称 |
功能 |
描述 |
DiscountService |
DiscountResult sendDiscount(String uid,String number) |
模拟打折券服务 |
YouKuMemberService |
void openMember(String bindMobile , String number) |
模拟赠送优酷会员服务 |
SmallGiftService |
Boolean giveSmallGift(SmallGiftInfo smallGiftInfo) |
模拟礼品服务 |
服务层定义
public class DiscountService {
public DiscountResult sendDiscount(String uid, String number){
System.out.println("向用户发放打折券一张: " + uid + " , " + number);
return new DiscountResult("200","发放打折券成功");
}
}
public class YouKuMemberService {
public void openMember(String bindMobile , String number){
System.out.println("发放优酷会员: " + bindMobile + " , " + number);
}
}
public class SmallGiftService {
public Boolean giveSmallGift(SmallGiftInfo smallGiftInfo){
System.out.println("小礼品已发货,获奖用户注意查收! " + JSON.toJSON(smallGiftInfo));
return true;
}
}
控制层
名称 |
功能 |
描述 |
DeliverController |
ResponseResult awardToUser(AwardInfo awardInfo) |
按照类型的不同发放商品 |
控制层
public class DeliverController {
/**
* 按照类型的不同发放商品
* 奖品类型: 1 打折券 ,2 优酷会员,3 小礼品
*/
public void awardToUser(AwardInfo awardInfo){
if(awardInfo.getAwardType() == 1){ //打折券
DiscountService discountService = new DiscountService();
DiscountResult result = discountService.sendDiscount(awardInfo.getUid(), awardInfo.getAwardNumber());
System.out.println("打折券发放成功!"+ JSON.toJSON(result));
}else if(awardInfo.getAwardType() == 2){ //优酷会员
//获取用户手机号
String bindMobile = awardInfo.getExtMap().get("phone");
//调用service
YouKuMemberService youKuMemberService = new YouKuMemberService();
youKuMemberService.openMember(bindMobile,awardInfo.getAwardNumber());
System.out.println("优酷会员发放成功!");
}else if(awardInfo.getAwardType() == 3){ /*
小礼品
封装收货用户信息
*/
SmallGiftInfo smallGiftInfo = new SmallGiftInfo();
smallGiftInfo.setUserName(awardInfo.getExtMap().get("username"));
smallGiftInfo.setOrderId(UUID.randomUUID().toString());
smallGiftInfo.setRelAddress(awardInfo.getExtMap().get("adderss"));
SmallGiftService smallGiftService = new SmallGiftService();
Boolean isSuccess = smallGiftService.giveSmallGift(smallGiftInfo);
System.out.println("小礼品发放成功!" + isSuccess);
}
}
}
总结:对于上面的实现方式,如果我们有想要添加的新的奖品时,势必要改动DeliverController的代码,违反开闭原则而且如果有的抽奖接口出现问题,那么对其进行重构的成本会非常高.
除此之外代码中有一组if分支判断逻辑,现在看起来还可以,但是如果经历几次迭代和拓展,后续ifelse肯定还会增加.到时候接手这段代码的研发将会十分痛苦.
2.3.1 简单工厂模式
定义:简单工厂模式又叫做静态工厂方法模式(static Factory Method pattern),它是通过使用静态方法接收不同的参数来返回不同的实例对象.
实现方式:定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。
适用场景:
(1)需要创建的对象较少。
(2)客户端不关心对象的创建过程。
简单工厂模式重构代码
service
/**
* 免费商品发放接口
* @author spikeCong
* @date 2022/9/8
**/
public interface IFreeGoods {
ResponseResult sendFreeGoods(AwardInfo awardInfo);
}
impl
/**
* 模拟打折券服务
* @author spikeCong
* @date 2022/9/8
**/
public class DiscountFreeGoods implements IFreeGoods {
@Override
public ResponseResult sendFreeGoods(AwardInfo awardInfo) {
System.out.println("向用户发放一张打折券: " + awardInfo.getUid() + " , " + awardInfo.getAwardNumber());
return new ResponseResult("200","打折券发放成功!");
}
}
/**
* 小礼品发放服务
* @author spikeCong
* @date 2022/9/8
**/
public class SmallGiftFreeGoods implements IFreeGoods {
@Override
public ResponseResult sendFreeGoods(AwardInfo awardInfo) {
SmallGiftInfo smallGiftInfo = new SmallGiftInfo();
smallGiftInfo.setUserPhone(awardInfo.getExtMap().get("phone"));
smallGiftInfo.setUserName(awardInfo.getExtMap().get("username"));
smallGiftInfo.setAddress(awardInfo.getExtMap().get("address"));
smallGiftInfo.setOrderId(UUID.randomUUID().toString());
System.out.println("小礼品发放成,请注意查收: " + JSON.toJSON(smallGiftInfo));
return new ResponseResult("200","小礼品发送成功",smallGiftInfo);
}
}
/**
* 优酷 会员服务
* @author spikeCong
* @date 2022/9/8
**/
public class YouKuMemberFreeGoods implements IFreeGoods {
@Override
public ResponseResult sendFreeGoods(AwardInfo awardInfo) {
String phone = awardInfo.getExtMap().get("phone");
System.out.println("发放优酷会员成功,绑定手机号: " + phone);
return new ResponseResult("200","优酷会员发放成功!");
}
}
factory
/**
* 具体工厂: 生成免费商品
* @author spikeCong
* @date 2022/9/9
**/
public class FreeGoodsFactory {
public static IFreeGoods getInstance(Integer awardType){
IFreeGoods iFreeGoods = null;
if(awardType == 1){ //打折券
iFreeGoods = new DiscountFreeGoods();
}else if(awardType == 2){ //优酷会员
iFreeGoods = new YouKuMemberFreeGoods();
}else if(awardType == 3){ //小礼品
iFreeGoods = new SmallGiftFreeGoods();
}
return iFreeGoods;
}
}
controller
public class DeliverController {
//发放奖品
public ResponseResult awardToUser(AwardInfo awardInfo){
try {
IFreeGoods freeGoods = FreeGoodsFactory.getInstance(awardInfo.getAwardTypes());
ResponseResult responseResult = freeGoods.sendFreeGoods(awardInfo);
return responseResult;
} catch (Exception e) {
e.printStackTrace();
return new ResponseResult("201","奖品发放失败!");
}
}
}
测试
public class TestApi02 {
DeliverController deliverController = new DeliverController();
@Test
public void test01(){
//1. 发放打折券优惠
AwardInfo info1 = new AwardInfo();
info1.setUid("1001");
info1.setAwardTypes(1);
info1.setAwardNumber("DEL12345");
ResponseResult result = deliverController.awardToUser(info1);
System.out.println(result);
}
@Test
public void test02(){
//2. 发放优酷会员
AwardInfo info2 = new AwardInfo();
info2.setUid("1002");
info2.setAwardTypes(2);
info2.setAwardNumber("DW12345");
Map<String,String> map = new HashMap<>();
map.put("phone","13512341234");
info2.setExtMap(map);
ResponseResult result1 = deliverController.awardToUser(info2);
System.out.println(result1);
}
@Test
public void test03(){
//3. 发放小礼品
AwardInfo info3 = new AwardInfo();
info3.setUid("1003");
info3.setAwardTypes(3);
info3.setAwardNumber("SM12345");
Map<String,String> map2 = new HashMap<>();
map2.put("username","大远");
map2.put("phone","13512341234");
map2.put("address","北京天安门");
info3.setExtMap(map2);
ResponseResult result2 = deliverController.awardToUser(info3);
System.out.println(result2);
}
}
总结:
优点:
1.封装了创建对象的过程,可以通过参数直接获取对象
2.把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
2.3.2 工厂方法重构代码(重要)
抽象工厂
public interface FreeGoodsFactory {
IFreeGoods getInstance();
}
具体工厂
public class DiscountFreeGoodsFactory implements FreeGoodsFactory {
@Override
public IFreeGoods getInstance() {
return new DiscountFreeGoods();
}
}
public class SmallGiftFreeGoodsFactory implements FreeGoodsFactory {
@Override
public IFreeGoods getInstance() {
return new SmallGiftFreeGoods();
}
}
controller
public class DeliverController {
/**
* 按照类型的不同发放商品
*/
public ResponseResult awardToUser(AwardInfo awardInfo){
FreeGoodsFactory freeGoodsFactory = null;
if(awardInfo.getAwardType() == 1){
freeGoodsFactory = new DiscountFreeGoodsFactory();
}else if(awardInfo.getAwardType() == 2){
freeGoodsFactory = new SmallGiftFreeGoodsFactory();
}
IFreeGoods freeGoods = freeGoodsFactory.getInstance();
System.out.println("=====工厂方法模式========");
ResponseResult result = freeGoods.sendFreeGoods(awardInfo);
return result;
}
}
工厂类对象的创建逻辑又耦合进了 awardToUser() 方法中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。
那怎么 来解决这个问题呢?
我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象
/**
* 用简单方法模式实现: 工厂的工厂,作用是不需要每次创建新的工厂对象
* @author spikeCong
* @date 2022/9/9
**/
public class FreeGoodsFactoryMap {
private static final Map<Integer,FreeGoodsFactory> cachedFactories = new HashMap<>();
static{
cachedFactories.put(1, new DiscountFreeGoodsFactory());
cachedFactories.put(2, new SmallGiftFreeGoodsFactory());
}
public static FreeGoodsFactory getParserFactory(Integer type){
if(type == 1){
FreeGoodsFactory freeGoodsFactory = cachedFactories.get(1);
return freeGoodsFactory;
}else if(type ==2){
FreeGoodsFactory freeGoodsFactory = cachedFactories.get(2);
return freeGoodsFactory;
}
return null;
}
}
/**
* 发放奖品接口
* @author spikeCong
* @date 2022/9/7
**/
public class DeliverController {
/**
* 按照类型的不同发放商品
*/
public ResponseResult awardToUser(AwardInfo awardInfo){
//根据类型获取工厂
FreeGoodsFactory goodsFactory = FreeGoodsFactoryMap.getParserFactory(awardInfo.getAwardType());
//从工厂中获取对应实例
IFreeGoods freeGoods = goodsFactory.getInstance();
System.out.println("=====工厂方法模式========");
ResponseResult result = freeGoods.sendFreeGoods(awardInfo);
return result;
}
}
现在我们的代码已经基本上符合了开闭原则,当有新增的产品时,我们需要做的事情包括:
- 创建新的产品类,并且让该产品实现抽象产品接口
- 创建产品类对应的具体工厂,并让具体工厂实现抽象工厂
- 将新的具体工厂对象,添加到FreeGoodsFactoryMap的cachedFactories中即可,需要改动的代码改动的非常少.
2.3.3 抽象工厂(重要)
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):它声明了一种用于创建一族产品的方法,每一个方法对应一种产品.
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建.
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
抽象工厂
public interface AppliancesFactory {
AbstractTV createTV();
AbstractFreezer createFreezer();
}
具体工厂
public class HairFactory implements AppliancesFactory {
@Override
public AbstractTV createTV() {
return new HairTV();
}
@Override
public AbstractFreezer createFreezer() {
return new HairFreezer();
}
}
public class HisenseFactory implements AppliancesFactory {
@Override
public AbstractTV createTV() {
return new HisenseTV();
}
@Override
public AbstractFreezer createFreezer() {
return new HisenseFreezer();
}
}
具体工厂: 每一个具体工厂方法,可以返回一个特定的产品对象,而同一个具体工厂所创建的产品对象构成了一个产品族.
抽象产品
public interface AbstractFreezer {}
public interface AbstractTV {}
具体产品
public class HairFreezer implements AbstractFreezer {}
public class HisenseFreezer implements AbstractFreezer {}
public class HairTV implements AbstractTV {}
public class HisenseTV implements AbstractTV {}
客户端
@Data
public class Client {
private AbstractTV tv;
private AbstractFreezer freezer;
public Client(AppliancesFactory factory){
//在客户端看来就是使用抽象工厂来生产家电
this.tv = factory.createTV();
this.freezer = factory.createFreezer();
}
public static void main(String[] args) {
Client client = new Client(new HisenseFactory());
AbstractTV tv = client.getTv();
System.out.println(tv);
AbstractFreezer freezer = client.getFreezer();
System.out.println(freezer);
}
}
抽象工厂模式总结:
优点:
对于不同产品系列有比较多共性特征时,可以使用抽象工厂模式,有助于提升组件的复用性.
当需要提升代码的扩展性并降低维护成本时,把对象的创建和使用过程分开,能有效地将代码统一到一个级别上
解决跨平台带来的兼容性问题
缺点:
增加新的产品等级结构麻烦,需要对原有结构进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大不变,违背了开闭原则
2.4 建造者模式
定义: 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
建造者(Builder)模式包含以下4个角色 :
抽象建造者类(Builder):这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供一个方法,返回创建好的负责产品对象。
产品类(Product):要创建的复杂对象 (包含多个组成部件).
指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建(客户端一般只需要与指挥者进行交互)。
2.4.1 建造者模式1
创建共享单车
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
具体产品
@Data
public class Bike {
//车架
private String frame;
//座椅
private String seat;
}
构建者类
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
public class HelloBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("碳纤维车架");
}
@Override
public void buildSeat() {
mBike.setSeat("橡胶车座");
}
@Override
public Bike createBike() {
return mBike;
}
}
public class MobikeBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("铝合金车架");
}
@Override
public void buildSeat() {
mBike.setSeat("真皮车座");
}
@Override
public Bike createBike() {
return mBike;
}
}
指挥者类
public class Director {
private Builder mBuilder;
public Director(Builder builder) {
this.mBuilder = builder;
}
public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
return mBuilder.createBike();
}
}
客户端
public class Client {
public static void main(String[] args) {
showBike(new HelloBuilder());
showBike(new MobikeBuilder());
}
private static void showBike(Builder builder) {
Director director = new Director(builder);
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
2.4.2 建造者模式2
错误案例:
构造方法如果参数过多,代码的可读性和易用性都会变差. 在使用构造函数时,很容易搞错参数的顺序,传递进去错误的参数值,导致很有隐蔽的BUG出现.
public class RabbitMQClient1 {
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 1000;
//构造方法参数过多,代码的可读性和易用性太差,在使用构造函数时,很容易搞错顺序,传递错误的参数值,导致很有隐蔽的BUG
public RabbitMQClient1(String host, int port, int mode, String exchange, String queue, boolean isDurable, int connectionTimeout) {
this.host = host;
this.port = port;
this.mode = mode;
this.exchange = exchange;
this.queue = queue;
this.isDurable = isDurable;
this.connectionTimeout = connectionTimeout;
if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有
if(exchange != null){
throw new RuntimeException("工作队列模式无需设计交换机");
}
if(queue == null || queue.trim().equals("")){
throw new RuntimeException("工作队列模式名称不能为空");
}
if(isDurable == false){
throw new RuntimeException("工作队列模式必须开启持久化");
}
}else if(mode == 2){ //路由模式必须设计交换机,但是不能设计队列
if(exchange == null){
throw new RuntimeException("路由模式下必须设置交换机");
}
if(queue != null){
throw new RuntimeException("路由模式无须设计队列名称");
}
}
//其他验证方式,
}
public void sendMessage(String msg){
System.out.println("发送消息......");
}
public static void main(String[] args) {
//每一种模式,都需要根据不同的情况进行实例化,构造方法会变得过于复杂.
RabbitMQClient1 client1 = new RabbitMQClient1("192.168.52.123",5672,
2,"sample-exchange",null,true,5000);
client1.sendMessage("Test-MSG");
}
}
建造者使用步骤如下:
- 目标类的构造方法要传入Builder对象
- Builder建造者类位于目标类内部,并且使用static修饰
- Builder建造者对象提供内置的各种set方法,注意set方法返回的是builder对象本身
- Builder建造者类提供build()方法实现目标对象的创建
public class 目标类{
//目标类的构造方法需要传入Builder对象
public 目标类(Builder builder){
}
public 返回值 业务方法(参数列表){
}
//Builder建造者类位于目标类内部,并且使用static修饰
public static class Builder(){
//Builder建造者对象提供内置的各种set方法,注意set方法返回的是builder对象本身
private String xxx;
public Builder setXxx(String xxx){
this.xxx = xxx;
return this;
}
//Builder建造者类提供build()方法实现目标对象的创建
public 目标类 build(){
//校验
return new 目标类(this);
}
}
}
重写案例代码
/**
* 建造者模式
* @author spikeCong
* @date 2022/9/19
**/
public class RabbitMQClient {
//私有构造方法
private RabbitMQClient(Builder builder) {
}
public static class Builder{
//属性密闭性,保证对象不可变
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 1000;
public Builder setHost(String host) {
this.host = host;
return this;
}
public Builder setPort(int port) {
this.port = port;
return this;
}
public Builder setMode(int mode) {
this.mode = mode;
return this;
}
public Builder setExchange(String exchange) {
this.exchange = exchange;
return this;
}
public Builder setQueue(String queue) {
this.queue = queue;
return this;
}
public Builder setDurable(boolean durable) {
isDurable = durable;
return this;
}
public Builder setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
return this;
}
//返回构建好的复杂对象
public RabbitMQClient build(){
//首先进行校验
if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有
if(exchange != null){
throw new RuntimeException("工作队列模式无需设计交换机");
}
if(queue == null || queue.trim().equals("")){
throw new RuntimeException("工作队列模式名称不能为空");
}
if(isDurable == false){
throw new RuntimeException("工作队列模式必须开启持久化");
}
}else if(mode == 2){ //路由模式必须设计交换机,但是不能设计队列
if(exchange == null){
throw new RuntimeException("路由模式下必须设置交换机");
}
if(queue != null){
throw new RuntimeException("路由模式无须设计队列名称");
}
}
return new RabbitMQClient(this);
}
}
public void sendMessage(String msg){
System.out.println("发送消息......");
}
}
测试:
public class MainAPP {
public static void main(String[] args) {
//使用链式编程设置参数
RabbitMQClient client = new RabbitMQClient.Builder().setHost("192.168.52.123").setMode(2).setExchange("text-exchange")
.setPort(5672).setDurable(true).build();
client.sendMessage("Test");
}
}
2.4.3 建造者模式总结
1. 建造者模式与工厂模式区别
- 工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
- 建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。
2. 建造者模式的优缺点
优点
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
3. 应用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
2.5 原型模式
定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式包含如下角色:
- 抽象原型类(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口.
- 具体原型类(ConcretePrototype):实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象.
- 客户类(Client):在客户类中,让一个原型对象克隆自身从而创建一个新的对象.由于客户类针对抽象原型类Prototype编程.因此用户可以根据需要选择具体原型类,系统具有较好的扩展性,增加或者替换具体原型类都比较方便.
2.5.1 浅克隆代码实现
public class ConcretePrototype implements Cloneable {
public ConcretePrototype() {
System.out.println("具体的原型对象创建完成!");
}
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
System.out.println("具体的原型对象复制成功!");
return (ConcretePrototype)super.clone();
}
}
测试
@Test
public void test01() throws CloneNotSupportedException {
ConcretePrototype c1 = new ConcretePrototype();
ConcretePrototype c2 = c1.clone();
System.out.println("对象c1和c2是同一个对象?" + (c1 == c2));
}
2.5.2 深克隆代码实现
在ConcretePrototype类中添加一个对象属性为Person类型
这里还是浅拷贝
public class ConcretePrototype implements Cloneable {
private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
void show(){
System.out.println("嫌疑人姓名: " +person.getName());
}
public ConcretePrototype() {
System.out.println("具体的原型对象创建完成!");
}
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
System.out.println("具体的原型对象复制成功!");
return (ConcretePrototype)super.clone();
}
}
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试
@Test
public void test02() throws CloneNotSupportedException {
ConcretePrototype c1 = new ConcretePrototype();
Person p1 = new Person();
c1.setPerson(p1);
//复制c1
ConcretePrototype c2 = c1.clone();
//获取复制对象c2中的Person对象
Person p2 = c2.getPerson();
p2.setName("xxx");
//判断p1与p2是否是同一对象
System.out.println("p1和p2是同一个对象?" + (p1 == p2));
c1.show();
c2.show();
}
深拷贝:如果有需求场景中不允许共享同一对象,那么就需要使用深拷贝,如果想要进行深拷贝需要使用到对象序列化流 (对象序列化之后,再进行反序列化获取到的是不同对象). 代码如下: 注意深拷贝被拷贝的对象person需要实现Serializable接口
@Test
public void test03() throws Exception {
ConcretePrototype c1 = new ConcretePrototype();
Person p1 = new Person("xxx1");
c1.setPerson(p1);
//创建对象序列化输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c.txt"));
//将c1对象写到文件中
oos.writeObject(c1);
oos.close();
//创建对象序列化输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c.txt"));
//读取对象
ConcretePrototype c2 = (ConcretePrototype) ois.readObject();
Person p2 = c2.getPerson();
p2.setName("xxx2");
//判断p1与p2是否是同一个对象
System.out.println("p1和p2是同一个对象?" + (p1 == p2));
c1.show();
c2.show();
}
模拟某银行电子账单系统的广告信发送功能,广告信的发送都是有一个模板的,从数据库查出客户的信息,然后放到模板中生成一份完整的邮件,然后交给发送机进行发送处理.
广告模板代码
public class AdvTemplate {
//广告信名称
private String advSubject = "xx银行本月还款达标,可抽iPhone 13等好礼!";
//广告信内容
private String advContext = "达标用户请在2022年3月1日到2022年3月30参与抽奖......";
public String getAdvSubject() {
return this.advSubject;
}
public String getAdvContext() {
return this.advContext;
}
}
邮件类代码
@Data
public class Mail {
//收件人
private String receiver;
//邮件名称
private String subject;
//称谓
private String appellation;
//邮件内容
private String context;
//邮件尾部, 一般是"xxx版权所有"等信息
private String tail;
//构造函数
public Mail(AdvTemplate advTemplate) {
this.context = advTemplate.getAdvContext();
this.subject = advTemplate.getAdvSubject();
}
}
客户类
public class Client {
//发送信息的是数