阅读学习《重学java设计模式》;这本书已经放在自己的阿里云云盘的【pdf书籍】目录下了;pdf中有作者的公众号,公众号可以获取相关资料、源码和别的内容的资料;很不错;
源码地址:https://github.com/fuzhengwei/CodeGuide
🌟0 设计模式的最大原则就是面向接口编程!🌟
后续所有的设计基本都是遵循这样的原则,要么是接口要么是抽象类;这样才能很好的用到多态;
一、 建造者型
1.1 工厂方法模式
要点: 我们针对不同的产品提供一个工厂类;根据参数有工厂绝对返回什么对象,并且这个些对象抽象一个接口出来,所有产品都去实现这个接口;这样工厂返回的对象就能用多态的特性去调用接口中的方法了;
工厂
public class StoreFactory {
public ICommodity getCommodityService(Integer commodityType) {
if (null == commodityType) return null;
if (1 == commodityType) return new CouponCommodityService();
if (2 == commodityType) return new GoodsCommodityService();
if (3 == commodityType) return new CardCommodityService();
throw new RuntimeException("不存在的商品服务类型");
}
-----------------------------------------------------------------
接口
public interface ICommodity {
//定义统一方法 生成对象的接口方法;
void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
}
----------------------------------------------------
不同产品的实现
public class CardCommodityService implements ICommodity {
重写sendCommodity方法
}
public class GoodsCommodityService implements ICommodity {
重写sendCommodity方法
}
----------------------------------------------------
测试:
// 1. 优惠券
ICommodity commodityService_1 = storeFactory.getCommodityService(1);
commodityService_1.sendCommodity("10001", "EGM1023938910232121323432", "791098764902132", null);
// 3. 第三方兑换卡(爱奇艺)
ICommodity commodityService_3 = storeFactory.getCommodityService(3); commodityService_3.sendCommodity("10001","AQY1xjkUodl8LO975GdfrYUio",null,null);
1.1.1 总结
工厂方法设计模式代码很简单,就是一个【1工厂类】+【1个接口类】+【不同产品对接口的不同实现,n个】,工厂根据不同参数决定产生具体实现类的对象,不同实现类对接口的方法实现方式不一致,调用方调用工厂方法获取不同的对象并调用接口方法实现不同代码逻辑的执行;
优点: 避免创建者与具体产品的逻辑耦合,满足单一职责,每一个业务逻辑实现都在所属自己的类中完成,满足开闭原则,无需更改使用调用方法就可以在过程中引入新的产品类型;
缺点: 有非常多的产品类型的时候,每一个产品类型都需要实现接口,这样类的数量会爆炸增长的;但是如果产品数量不多的话用工厂方法模式很合适,如果多的话就需要用后边的有抽象工厂进行优化了;
1.2 抽象工厂模式
抽象工厂模式与工厂方法模式虽然主要意图都是为了解决“接口选择”问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。
这个《重学java设计模式》中的例子有点个飘,晚点找个别的文章再对比一下的;
1.3 建造者模式
建造者模式主要解决的问题是在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的过程构成;由于需求的变化,这个复杂对象的各个部分经常面临着重大的变化,但是将它们组合在一起的过程却相对稳定。 这里我们会把构建的过程交给创建者类,而创建者通过使用我们的构建工具包,去构建出不同的装修套餐。
用我们自己的话说,就是如果一个对象是由很多独立的 且固定的子对象组成的;并且这些子对象还能自由组合,能组合成不同的套餐;那我们适合将这个复杂对象先抽建一个接口,并让这个复杂对象实现这个接口,构建一个建造者,在这里对复杂对象进行组合,然后调用的地方调用构建者返回一个复杂对象;再用接口对象(复杂对象的引用)调用接口的方法
--- 调用者,Builder是建造者,建造者是返回的复杂对象的接口对象(IMenu)
@Test
public void test_Builder(){
Builder builder = new Builder();
// 豪华欧式
System.out.println(builder.levelOne(132.52D).getDetail());
// 轻奢田园
System.out.println(builder.levelTwo(98.25D).getDetail());
}
--- 建造者 ,在这里实现对复杂对象DecorationPackageMenu的不同子对象的组合
public class Builder {
public IMenu levelOne(Double area) {
return new DecorationPackageMenu(area, "豪华欧式")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new DuluxCoat()) // 涂料,多乐士
.appendFloor(new ShengXiangFloor()); // 地板,圣象
}
public IMenu levelTwo(Double area){
return new DecorationPackageMenu(area, "轻奢田园")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new LiBangCoat()) // 涂料,立邦
.appendTile(new MarcoPoloTile()); // 地砖,马可波罗
}
}
-- 复杂对象本身 DecorationPackageMenu对象本身由很多的Matter对象组成
public class DecorationPackageMenu implements IMenu {
private List<Matter> list = new ArrayList<Matter>(); // 装修清单
复杂对象由很多Matter子对象组成,而且子对象也是父类(接口),他也有很多具体子类(实现类);造成这个复杂对象更加复杂了;那我们将复杂对象的所有固定组成部分(父类、接口)的组装方法抽象成一个方法,并放到IMenu接口中;
private BigDecimal price = BigDecimal.ZERO; // 装修价格
private BigDecimal area; // 面积
private String grade; // 装修等级;豪华欧式、轻奢田园、现代简约
private DecorationPackageMenu() {
}
public DecorationPackageMenu(Double area, String grade) {
this.area = new BigDecimal(area);
this.grade = grade;
}
public IMenu appendCeiling(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
return this;
}
public IMenu appendCoat(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
return this;
}
public IMenu appendFloor(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
public IMenu appendTile(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
public String getDetail() {
StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
"装修清单" + "\r\n" +
"套餐等级:" + grade + "\r\n" +
"套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
"房屋面积:" + area.doubleValue() + " 平米\r\n" +
"材料清单:\r\n");
for (Matter matter: list) {
detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
}
return detail.toString();
}
}
-- 复杂对象固定组成的抽象类 (如果装修需要【吊顶、涂料、地板】等等,那就给装修这个复杂对象的怎么都要有的【吊顶、涂料】等抽象成一个方法,具体吊什么顶,涂什么涂料都放到间照着类中去玩;)
public interface IMenu {
/**吊顶*/
IMenu appendCeiling(Matter matter);
/** 涂料*/
IMenu appendCoat(Matter matter);
/**地板*/
IMenu appendFloor(Matter matter);
/**地砖*/
IMenu appendTile(Matter matter);
/**明细*/
String getDetail();
}
总结
● 通过上面对建造者模式的使用,已经可以摸索出一点心得。那就是什么时候会选择这样的设计模
式,当:一些基本物料不会变,而其组合经常变化的时候,就可以选择这样的设计模式来构建代码。
● 此设计模式满足了单一职责原则以及可复用的技术、建造者独立、易扩展、便于控制细节风险。但
同时当出现特别多的物料以及很多的组合后,类的不断扩展也会造成难以维护的问题。但这种设计
结构模型可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中大量的重复。
● 设计模式能带给你的是一些思想,但在平时的开发中怎么样清晰的提炼出符合此思路的建造模块,
是比较难的。需要经过一些锻炼和不断承接更多的项目,从而获得这部分经验。有的时候你的代码
写的好,往往是倒逼的,复杂的业务频繁的变化,不断的挑战!
另一种表述
我感觉这个例子更好理解 http://c.biancheng.net/view/1354.html
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的
该模式的主要优点如下:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
其缺点如下:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
二、 行为型
2.1 迭代器模式(不重要了)
这个的作用就是,遍历容器中的每个元素的;但是吧,由于jdk1.2引入了Java.util.Iterator ,java中的所有的集合类(容器)都已经变相的实现这个东西了,所以语言提供的容器已经天然支持迭代器模式了,不需要我们去写这个模式了,这个模式不用研究了,看看示例中的源码,了解下怎么玩转就好了;(具体的可以看看 ArrayList 中,他有个内部类 private class Itr implements Iterator<E>
,这个内部类其实就是抽象迭代器的一个具体实现,这个东西完成了对容器得遍历)
说他不重要了是因为我更倾向于
1 类图
● Iterator抽象迭代器
(这个角色就直接用jdk的Iterator就行了,也可以写个自己的接口,不过感觉没必要)
抽象迭代器负责定义访问和遍历元素的接口,而且基本上是有固定的3个方法:first()获得第一个元素,next()访问下一个元素,isDone()是否已经访问到底部(Java叫做hasNext()方法)
● ConcreteIterator具体迭代器
具体迭代器角色要实现迭代器接口,完成容器元素的遍历。
● Aggregate抽象容器
容器角色负责提供创建具体选代器角色的接口,必然提供一个类似createlterator()这样的方法,在Java中一般是iterator()方法。
● Concrete Aggregate具体容器
具体容器实现容器接口定义的方法,创建出容纳迭代器的对象。
2 示例1
package com.binc.testspring.study.design.iterator;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.util.Iterator;
import java.util.Vector;
/**
* FileName: TestIterator 玩玩迭代器模式
*
* 1. 抽象一个迭代器接口
* 2. 抽象一个容器
* 3. 具体的迭代器类
* 4 具体的容器类
*
*
* Autho: binC
* Date: 2022/3/31 10:39
*/
public class TestIterator {
public static void main(String[] args) {
BincUser b1 = new BincUser(1,"Binc一号");
BincUser b2 = new BincUser(12,"Binc二号");
BincUser b3 = new BincUser(123,"Binc3号");
ConcreteAggregate concreteAggregate = new ConcreteAggregate();
//具体容器的添加
concreteAggregate.add(b1);
concreteAggregate.add(b2);
concreteAggregate.add(b3);
//容器的遍历
Interator interator = concreteAggregate.interator();
while (interator.hasNext()) {
BincUser next = (BincUser) interator.next();
System.out.println("====遍历结果"+next.toString());
}
}
}
//1 抽象迭代器接口,我们的迭代器就先定义两个方法
// 如果直接用jdk的 Iterator其实更方便
interface Interator{
boolean hasNext();
Object next();
}
//3 具体迭代器 这里就像是ArrayList类中的那个内部迭代器类 private class Itr implements Iterator<E> {
class ConcreteIterator implements Interator{
private Vector vector = new Vector(); //定义一个容器
private int cursor = 0 ;// 定义一个游标
//构造函数,通过构造函数将具体迭代器的容器完成赋值
public ConcreteIterator(Vector _vector) {
this.vector = _vector;
}
@Override
public boolean hasNext() {
if (this.cursor >= vector.size() ) {
return false;
}else {
return true;
}
}
@Override
public Object next() {
Object result = null;
if (this.hasNext()) {
result = this.vector.get(this.cursor++);
} else {
result = null;
}
return result;
}
}
// 2 抽象容器
interface Aggregate{
//增加元素
void add(Object obj);
// 返回我们自定义的迭代器,然后用得得带起进行遍历
Interator interator();
}
// 4 具体的容器
class ConcreteAggregate implements Aggregate{
private Vector vector = new Vector();
@Override
public void add(Object obj) {
this.vector.add(obj);
}
@Override
public Interator interator() {
return new ConcreteIterator(this.vector);
}
}
// 5 javabean
@Data
@ToString
@Builder
class BincUser {
private int age;
private String name;
}
3 示例2
为什么要在集合之外引入Iterator角色?
引入Iterator后可以将遍历与实现分离开来
这里使用了Iterator的hasNext方法和next方法,并没有调用BookShelf的方法。也就是说,这里的while循环并不依赖于BookShelf的实现
package com.binc.testspring.study.design.iterator;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.ToString;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.function.Consumer;
/**
* FileName: TestIterator_binc 测试迭代器模式 可移植版
*
* 讲解:
* 我们有一个 书 类; 让后有一个书架类 bookshelf ;书架相当于一个容器;里边有很多的书;正常来说我们需要遍历书架的书的话,我们需要取得书架对象,
* 然后通过书架对象获取书的集合,让后遍历书的集合就行了; 或者在书架类内写一个遍历的方法;
* 这个时候考虑这么一个场景,我们书的集合用的是List集合,那我们变了的话就要用 foreahc fori等方式,不停的get;如果书的集合是数组呢;那我们无论是自己通过书
* 架对象获得集合然后遍历还是书架类内写个遍历的方法;那这个实现明显跟list集合就不一样了;为了适应这样的场景,我们采用迭代器模式;我们像遍历书架内书的集合的话,
* 我们去获取书架自己的迭代器,然后用这个迭代器去实现遍历;
*
* 为什么要在集合之外引入Iterator角色?
* 引入Iterator后可以将遍历与实现分离开来
* 这里使用了Iterator的hasNext方法和next方法,并没有调用BookShelf的方法。也就是说,这里的while循环并不依赖于BookShelf的实现
*
* Autho: binC
* Date: 2022/4/1 11:14
*/
public class TestIterator_binc {
public static void main(String[] args) {
Book yuwen = new Book("语文",11);
Book yingyu = new Book("英语",112);
Book shuxue = new Book("数学",211);
List<Book> list = Lists.newArrayList(yuwen,shuxue,yingyu);
Vector vector = new Vector();
vector.add(yuwen);
vector.add(shuxue);
vector.add(yingyu);
BookShelf bs1 = new BookShelf(vector);
BookShelf bs2 = new BookShelf(list);
Iterator iterator = bs1.iterator(); //这里使用了Iterator的hasNext方法和next方法,并没有调用BookShelf的方法。也就是说,这里的while循环并不依赖于BookShelf的实现
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Iterator iterator1 = bs2.iterator2(); //这里使用了Iterator的hasNext方法和next方法,并没有调用BookShelf的方法。也就是说,这里的while循环并不依赖于BookShelf的实现
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
}
}
//1 定义一个书架类 这是一个很纯粹的类,关于遍历的东西都不写到这里,给他准备一个类(用于书架中书的遍历)MyIteratro
class BookShelf{
private Vector<Book> books_vector = new Vector<>();
private List<Book> bookList ;
public BookShelf(Vector<Book> books_vector) {
this.books_vector = books_vector;
}
public BookShelf(List<Book> books_array) {
this.bookList = books_array;
}
Iterator iterator(){
return new BookShelfIterator_vector(this.books_vector);
}
Iterator iterator2(){
return new BookShelfIterator_array(this.bookList);
}
}
// 2 定义一个书的类
@Data
@ToString
class Book{
String name;
int price;
public Book(String name, int price) {
this.name = name;
this.price = price;
}
}
// 3 定一个 单独用于书架中书的遍历的的一个类 实现jdk的Iterator有点坏处;就是这个接口的方法有点多,我们实际上只想要两个方法; 这个时候感觉还是自己定义一个 只有两个方法的MyIterator有优势了;
// 我们只重写 hasNext 和 next 两个方法;
class BookShelfIterator_vector implements Iterator{
private Vector vector = new Vector();
private int cursor = 0 ;// 定义一个游标
public BookShelfIterator_vector(Vector vector) {
this.vector = vector;
}
@Override
public boolean hasNext() {
if (this.cursor >= vector.size() ) {
return false;
}else {
return true;
}
}
@Override
public Object next() {
Object result = null;
if (this.hasNext()) {
result = this.vector.get(this.cursor++);
} else {
result = null;
}
return result;
}
}
// 3-2 定一个 单独用于书架中书的遍历的的一个类 实现jdk的Iterator有点坏处;就是这个接口的方法有点多,我们实际上只想要两个方法; 这个时候感觉还是自己定义一个 只有两个方法的MyIterator有优势了;
// 我们只重写 hasNext 和 next 两个方法;
class BookShelfIterator_array implements Iterator {
private List<Book> vector;
private int cursor = 0;// 定义一个游标
public BookShelfIterator_array(List<Book> vector) {
this.vector = vector;
}
@Override
public boolean hasNext() {
if (this.cursor >= vector.size()) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
Object result = null;
if (this.hasNext()) {
result = this.vector.get(this.cursor++);
} else {
result = null;
}
return result;
}
}
三、 结构型
3.1 适配器🌟🌟🌟🌟🌟
如果是新设计的项目一定使用不到这个模式的,新项目考虑别的总比这个要好;用这个模式的是因为项目已经正常运行了,但是又要加入新的东西,但是接口(方法)与老的不一样;我们写一个中间转换器(适配器),将他们串起来
1 讲解
现状我们就对接了两个银行,客户端调用不同银行的实现;
突然领导有一天说,我们别的项目组调用了北京银行的接口,并封装了一下, 我们直接调他们;注意这个地方银行是现成的,并且方法名 逻辑 返回值 属性名 等等都略有区别,但是大差不大我们想要的东西都是由的,但是不跟我们IbankService标准接口兼容而已;那我们就为了不修改者地方银行的类;我们加一个适配器(中间转换类)
做一个适配器,1 【继承目标类】、【关联目标】类;2 实现银行接口,那就相当于适配器就是银行的第三类是实现了;然后我们在适配器的queryAccount方法中调用,beijingBank的 getBaseInfo+getBanlance两个方法把需要的东西全都提取出来,重新封装一个结果返回;这样就是先了客户端调用接口,然后适配器适配现成的工具(与现成接口步兼容)并获取到结果;
继承:类适配器
关联:对象适配器;推荐这个
上代码:(描述 client 、 iBankService、 cibBank、 ccbBank本来就是稳定运行的,现在要对接新的银行BeijingBank,这个也是现成的,不用重新写,所以结构不太一样,所以搞个适配器,对接IbankService和BejingBank)
2 示例
package com.binc.testspring.study.design.factory.adapter;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.junit.Test;
import java.math.BigDecimal;
/**
* FileName: AdapterTest 演练适配器模式
* Autho: binC
* Date: 2022/3/18 11:39
*/
public class AdapterTest {
//假装客户端
@Test
public void test(){
//1 调用一般接口
IbankService cibbank = new CIBBank();
System.out.println(cibbank.queryAccount(123L).toString());
cibbank.withdraw(123L,new BigDecimal("1"));
//2 适配器
IbankService bj = new BJAdapter(new BeijingBank());
bj.withdraw(123123L,new BigDecimal("2"));
System.out.println(bj.queryAccount(12123L).toString());
System.out.println("11111111111111111111111");
}
}
//银行账户
@Data
@Builder
@ToString
class Account{
private Long cardNo;//银行卡号
private String name;//账户名
private BigDecimal balance;//余额
}
//定义银行接口
interface IbankService{
/*获取银行账户信息*/
Account queryAccount(Long cardNo);
/*取钱接口*/
void withdraw(Long cardNo,BigDecimal balance);
}
//兴业银行实现
class CIBBank implements IbankService{
@Override
public Account queryAccount(Long cardNo) {
return Account.builder().cardNo(123L).name("兴业用户1").balance(BigDecimal.ZERO).build();
}
@Override
public void withdraw(Long cardNo, BigDecimal balance) {
System.out.println("兴业帐户"+cardNo+"提现"+ String.valueOf(balance)+"元");
}
}
//建行银行实现
class CCBBank implements IbankService{
@Override
public Account queryAccount(Long cardNo) {
return Account.builder().cardNo(999L).name("建行用户2").balance(BigDecimal.ONE).build();
}
@Override
public void withdraw(Long cardNo, BigDecimal balance) {
System.out.println("建行帐户"+cardNo+"提现"+ String.valueOf(balance)+"元");
}
}
///////////////////////////////////////////////////////////////////////////
class BeijingBank {
public String getBaseInfo(Long cardNo) {
return "北京银行用户3";
}
public BigDecimal getBalance(Long cardNo){
return new BigDecimal("123.01");
}
public void withdrawMenery(Long cardNo, BigDecimal balance) {
System.out.println("北京银行帐户"+cardNo+"提现"+ String.valueOf(balance)+"元");
}
}
// 适配器类 这里将要是配的类作为成员变量适配进来了,所以叫类适配器;推荐这种方式,也可以继承BeijingBank不过不够灵活;
class BJAdapter implements IbankService {
BeijingBank bj;
//除了用构造器注入也可以用 set 或者接口方法参数;推荐这个
public BJAdapter(BeijingBank bj) {
this.bj = bj;
}
@Override
public Account queryAccount(Long cardNo) {
String baseInfo = bj.getBaseInfo(cardNo);
BigDecimal balance = bj.getBalance(cardNo);
Account build = Account.builder().cardNo(cardNo)
.name(baseInfo)
.balance(balance)
.build();
return build;
}
@Override
public void withdraw(Long cardNo, BigDecimal balance) {
bj.withdrawMenery(cardNo,balance);
}
}
3.2 门面模式🌟🌟🌟 外观模式 Facade Pattern
是一种比较常用的封装模式
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用
这个模式相当得简单,其实就是我们有一个相当复杂的子系统(对象),调用这个子系统(对象)得多个方法,而且这些方法有严格得执行顺序得时候,为了方便客户端得调用,将这些顺序规则之类的都卸载门面里边,我们只需要给门面传输固定的简单参数,然后由门面根据这些参数执行调用不同子系统(对象)的不同方法;
即:现有的子系统东西复杂,我们在他上边在套一层,客户端不与子系统直接交互,与门面交互,对客户端的要求降到最低(只要直到传啥就行,不用关系调用间的顺序和依赖之类的);
缺点:
门面模式最大的缺点就是不符合开闭原则,对修改关闭,对扩展开放,看看我们那个门面对象吧,它可是重中之重,一旦在系统投产后发现有一个小错误,你怎么解决?完全遵从开闭原则,根本没办法解决。继承?覆写?都顶不上用,唯一能做的一件事就是修改门面角色的代码,这个风险相当大,这就需要大家在设计的时候慎之又慎,多思考几遍才会有好收获
使用场景
● 为一个复杂的模块或子系统提供一个供外界访问的接口 ● 子系统相对独立——外界对子系统的访问只要黑箱操作即可 比如利息的计算问题,没有深厚的业务知识和扎实的技术水平是不可能开发出该子系统的,但是对于使用该系统的开发人员来说,他需要做的就是输入金额以及存期,其他的都不用关心,返回的结果就是利息,这时候,门面模式是非使用不可了。 ● 预防低水平人员带来的风险扩散 比如一个低水平的技术人员参与项目开发,为降低个人代码质量对整体项目的影响风险,一般的做法是“画地为牢”,只能在指定的子系统中开发,然后再提供门面接口进行访问操作。
3.3 观察者模式 🌟🌟🌟🌟🌟 订阅模式
观察者模式(Observer Pattern)也叫做发布订阅模式(Publish/subscribe)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新
我们将 被观察者IHanFeiZi + 被观察者的新接口Obesrable 合并之后,类图就变成如下了;更简单了:
● Subject被观察者 (主题,就是被订阅的,观察者相当于是订阅者;主题发生变化了,需要通知所有订阅者;)🌟🌟 定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。 由于被观察者对于观察者的操作是公用的,没必要让每个具体的被观察者都去实现这几个对于观察者的操作了,我们直接定义成抽象类,这几个关于观察者的操作方法,直接在本抽象类中实现就好了;其子类更干净; ● Observer观察者 观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。 ● ConcreteSubject具体的被观察者 定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。 关键是内部要聚合一个观察者的集合(订阅者的集合) ● ConcreteObserver具体的观察者 每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
示例
注意这里用被观察者用的不再是接口了,而是抽象类,原因就是有几个方法不需要每个具体被观察者都挨个去实现,所以放在父类中去写了,所以用抽象类比较合适;(jdk1.8之后也可以在interface中普通方法了;用default关键字;推荐还是用抽象类的方式;强迫症了;)
package com.binc.testspring.study.design.iterator;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
/**
* FileName: TestIterator 玩玩迭代器模式
*
* 1. 抽象一个迭代器接口
* 2. 抽象一个容器
* 3. 具体的迭代器类
* 4 具体的容器类
*
*
* Autho: binC
* Date: 2022/3/31 10:39
*/
public class TestIterator {
public static void main(String[] args) {
BincUser b1 = new BincUser(1,"Binc一号");
BincUser b2 = new BincUser(12,"Binc二号");
BincUser b3 = new BincUser(123,"Binc3号");
ConcreteAggregate concreteAggregate = new ConcreteAggregate();
//具体容器的添加
concreteAggregate.add(b1);
concreteAggregate.add(b2);
concreteAggregate.add(b3);
//容器的遍历
Interator interator = concreteAggregate.interator();
while (interator.hasNext()) {
BincUser next = (BincUser) interator.next();
System.out.println("====遍历结果"+next.toString());
}
}
}
//1 抽象迭代器接口,我们的迭代器就先定义两个方法
// 如果直接用jdk的 Iterator其实更方便
interface Interator{
boolean hasNext();
Object next();
}
//3 具体迭代器
class ConcreteIterator implements Interator{
private Vector vector = new Vector(); //定义一个容器
private int cursor = 0 ;// 定义一个游标
//构造函数,通过构造函数将具体迭代器的容器完成赋值
public ConcreteIterator(Vector _vector) {
this.vector = _vector;
}
@Override
public boolean hasNext() {
if (this.cursor >= vector.size() ) {
return false;
}else {
return true;
}
}
@Override
public Object next() {
Object result = null;
if (this.hasNext()) {
result = this.vector.get(this.cursor++);
} else {
result = null;
}
return result;
}
}
// 2 抽象容器
interface Aggregate{
//增加元素
void add(Object obj);
// 返回我们自定义的迭代器,然后用得得带起进行遍历
Interator interator();
}
// 4 具体的容器
class ConcreteAggregate implements Aggregate{
private Vector vector = new Vector();
@Override
public void add(Object obj) {
this.vector.add(obj);
}
@Override
public Interator interator() {
return new ConcreteIterator(this.vector);
}
}
// 5 javabean
@Data
@ToString
@Builder
class BincUser {
private int age;
private String name;
}
2观察者模式总结
1 优点
- 观察者和被观察者之间是抽象间的耦合;拓展新的观察者的话很方便
-
2 缺点
一个被观察者如果有很多的观察者,开发测试需要关注的东西有点多;(小问题)
- 如果观察者同事作为被观察者,他后边还有别的观察者,如此套娃下去,多级触发问题很大;(强烈建议限制再两级之间,最后就一级)
一个被观察者,有多个观察者的话,这多个观察者之间通知时候先后顺序的,这样从效率还是从如果发生异常角度来说都不太友好;(建议改成异步)
3 使用场景
4 观察者模式使用注意事项
广播链的问题:(缺点2),限制层级在两层内吧;
注意: 观察者模式(观察者套观察者的时候)和责任链模式的最大区别就是观察者广播链在传播的过程中消息是随时更改的(被观察者1通知观察者2的时候,传递了数据x,观察者2同时还有一层被观察者的身份,他的观察者是观察者3,这个时候他通知观察者3的时候的消息可以是x,也可以是Y,这个很随意;),它是由相邻的两个节点协商的消息结构;而责任链模式在消息传递过程中基本上保持消息不可变,如果要改变,也只是在原有的消息上进行修正。
3 拓展(🌟🌟🌟🌟🌟)
上图是我们自己玩的时候的类图,无论是 抽象的观察者还是抽象被观察者都是自己去定义的;其实jdk默认就提供了两个这样的类;我们直接用它更方便;这两个接口分别是: Java.util.Observable
(暴露狂,被观察者)、java.util.Observer
(观察者);类图如下;其实没什么变化;只是间我们自己些的两个接口换成了jdk提供的接口;代码的话,没啥变化,就是替换了两个抽象而已;
上代码:(以后直接这么用就行了)🌟🌟🌟🌟🌟
package com.binc.testspring.study.design.observer;
import java.util.Arrays;
import java.util.Observable;
import java.util.Observer;
/**
* FileName: TestObserver 观察者模式的拓展, 抽象的观察者 和 抽象的被观察者都是用的jdk的东西,不再是自行抽象了
*
* 这里推荐用抽象类 而不是说接口了
*
* 关键点就是在被观察的对象里聚合一个 观察者的集合,有需要通知观察者的时候,遍历这个集合,然后挨个调用观者的方法;
*
* Autho: binC
* Date: 2022/3/31 19:03
*/
public class TestObserver_EX {
public static void main(String[] args) {
Weixin weixin = new Weixin();
Ali ali = new Ali();
ConcreteSubject2 sub = new ConcreteSubject2(); //创建被观察者对象
sub.addObserver(weixin); // 这里是注册观察者
sub.addObserver(ali);
sub.subjectDo_1();
System.out.println("==========");
sub.subjectDo_2();
}
}
//被观察者的抽象,这里不在掺杂观察者操作的代码了,关于观察者的操作都在Observable中了
interface MySubject {
void subjectDo_1();
void subjectDo_2();
}
// 具体的被观察者者1 具体的主题 关键实现Obervable接口
class ConcreteSubject2 extends Observable implements MySubject{
/********下面是抽象类的抽象方法,需要具体被观察者去重写的*********/
@Override
public void subjectDo_1() {
System.out.println("被观察者执行了方法1....");
super.setChanged();
super.notifyObservers("1");//调用父类方法,通知所有的观察者们
}
@Override
public void subjectDo_2() {
System.out.println("被观察者执行了方法22222....");
super.setChanged();
super.notifyObservers("2");//调用父类方法,通知所有的观察者们
}
}
/**************************************/
//具体观察者1
class Weixin implements java.util.Observer {
public void obDoSomething(Object... objects) {
System.out.println("微信观察者得到通知了,参数是【"+ Arrays.toString(objects)+"】");
}
/**
* 每当观察到的对象发生变化时,都会调用此方法。应用程序调用 <tt>Observable<tt> 对象的 <code>notifyObservers<code> 方法来通知对象的所有观察者更改。
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
*/
@Override
public void update(Observable o, Object arg) {
obDoSomething(arg);
}
}
//具体观察者2
class Ali implements java.util.Observer{
public void obDoSomething(Object... objects) {
System.out.println("阿里观察者得到通知了,参数是【"+ Arrays.toString(objects)+"】");
}
/**
* 每当观察到的对象发生变化时,都会调用此方法。应用程序调用 <tt>Observable<tt> 对象的 <code>notifyObservers<code> 方法来通知对象的所有观察者更改。
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
*/
@Override
public void update(Observable o, Object arg) {
obDoSomething(arg);
}
}
4 实际应用中变形
- 被观察者通知观察者的时候的update方法,传递了两个参数,一个是被观察者本身,一个是dto;这个dto就是一个双方协商的javabean;
考虑到远程通信,这里数据最终可能会变成xml格式的;
- 观察者的相应;
观察者的收到通知肯定会有很多事情要做,那这里就有效率和体验的问题了;两种变形;1 多线程处理异步处理 2 缓存,优先给结果(队列)
- 被观察者自己做主
这是什么意思呢?被观察者的状态改变是否一定要通知观察者呢?不一定吧,在设计的时候要灵活考虑,否则会加重观察者的处理逻辑,一般是这样做的,对被观察者的业务逻辑doSomething方法实现重载,如增加一个doSomething(boolean isNotifyObs)方法,决定是否通知观察者,而不是在消息到达观察者时才判断是否要消费。
3.4 责任链
使(责任链路上)多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止;责任链模式的重点是在“链”上,由一条链去处理相似的请求在链中决定谁来处理这个请求,并返回相应的结果
1 示例
package com.binc.testspring.study.design.handler;
import lombok.Data;
import java.util.Objects;
/**
* FileName: TestHandler 玩转责任链模式
*
* 关键点:
* 抽象限一个责任链接口;然后后边有多个实现者,这些实现者在调用出设置一个执行链!!!(重要);
* 抽象类主要体重三个东西:
* 1 当前的处理级别
* 2 设置下一个处理者
* 3 定义具体处理人的处理方法 (抽象,待子类实现)
* 4 控制当前操作是否由当前处理人处理(final的,这个由父类控制,其实现仅仅负责具体的处理逻辑)
* 5 为了保证高层代码的简单,我们在多创建一个client ,设置责任链的不走在client中设置,返回第一个责任链对象就行了;
*
* Autho: binC
* Date: 2022/4/1 15:12
*/
public class TestHandler {
public static void main(String[] args) throws Exception {
/* 这里封装一个client 对高层代码更友好
Handler p1 = new Person_1();
Handler p2 = new Person_2();
Handler p3 = new Person_3();
p1.setNextHandler(p2);
p2.setNextHandler(p3); //设置责任链,即 任务处理顺序 是 p1>p2>p3
Request_binc request_binc = new Request_binc();
request_binc.setRequest_levle("C"); // 设置当前请求的处理界别是3,就是level=C 的人去处理
Response_binc response_binc = p1.handlerRequest(request_binc);
System.out.println("======结束了");
*/
HandlerClient hc = new HandlerClient();
Handler first = hc.getProcessHandlers();
Request_binc request_binc = new Request_binc();
request_binc.setRequest_levle("C"); // 设置当前请求的处理界别是3,就是level=C 的人去处理
Response_binc response_binc = first.handlerRequest(request_binc);
System.out.println("----------结束了");
}
}
// 定义个抽象责任链类
abstract class Handler{
//0 为了防止level给出不符合要求的值,这里提前定义一些;(模板方法) 子类设置具体的level的时候,就只能用这里的值
public final static String LEVEL_A = "A";
public final static String LEVEL_B = "B";
public final static String LEVEL_C = "C";
// 1 定义一个处理级别
private String levle ;
public Handler(String levle) {
this.levle = levle;
}
//2 设置下一个处理者
private Handler nextHandler;
public void setNextHandler(Handler _handler) {
this.nextHandler = _handler;
}
//3 子类真正的处理方法
abstract Response_binc processRequest(Request_binc request);//
//4 父类控制由谁真正处理
//具体有哪个责任人处理当前任务是由父类控制的,这样子类只负责处理的真正逻辑,单一职责!! 注意用的是final 禁止子类重写 重载
final Response_binc handlerRequest(Request_binc request) throws Exception {
if (Objects.equals(this.levle, request.getRequest_levle())) {
return this.processRequest(request);
} else {
if (this.nextHandler != null) {
return this.nextHandler.handlerRequest(request);
} else {
throw new Exception("没有下一个责任人了,处理失败! ");
}
}
}
}
// 具体处理人1
class Person_1 extends Handler {
public Person_1() {
super(Handler.LEVEL_A);//设置 person_1 的处理界别为A
}
@Override
Response_binc processRequest(Request_binc request) {
System.out.println("有person——1 处理当前任务");
return new Response_binc();
}
}
// 具体处理人2
class Person_2 extends Handler {
public Person_2() {
super(Handler.LEVEL_B);//设置 person_2 的处理界别为B
}
@Override
Response_binc processRequest(Request_binc request) {
System.out.println("有person——2 处理当前任务");
return new Response_binc();
}
}
// 具体处理人3
class Person_3 extends Handler {
public Person_3() {
super(Handler.LEVEL_C);//设置 Person_3 的处理界别为B
}
@Override
Response_binc processRequest(Request_binc request) {
System.out.println("有person——3 处理当前任务");
return new Response_binc();
}
}
// 5 封装客户端
class HandlerClient{
//设置责任链,并返回责任链中第一个处理者
Handler getProcessHandlers(){
Handler p1 = new Person_1();
Handler p2 = new Person_2();
Handler p3 = new Person_3();
p1.setNextHandler(p2);
p2.setNextHandler(p3); //设置责任链,即 任务处理顺序 是 p1>p2>p3
// 返回第一个处理者
return p1;
}
}
//请求参数实体
@Data
class Request_binc{
private String request_levle; // 请求被处理的级别 最好是用枚举
}
//返回结果
class Response_binc{}
抽象的处理者实现三个职责:一、是定义一个请求的处理方法handleMessage,唯一对外开放的方法;二、是定义一个链的编排方法setNext,设置下一个处理者;三是定义了具体的请求者必须实现的方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务processRequest
2 责任链模式优缺点
- 优点:
责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌(例如在J2EE项目开发中,可以剥离出无状态Bean由责任链处理),两者解耦,提高系统的灵活性。
- 缺点:
责任链有两个非常显著的缺点:一是性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。二是调试不很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂
- 注意事项:
链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能
3总结
在例子和通用源码中Handler是抽象类,融合了模板方法模式,每个实现类只要实现两1个方法:processRequest方法处理请求(也可以在抽象出一个方法获取处理级别,每个实现类返回不同的值就行了(getHandlerLevel获得处理级别)), 想想单一职责原则和迪米特法则吧,通过融合模板方法模式,各个实现类只要关注的自己业务逻辑就成了,至于说什么事要自己处理,那就让父类去决定好了,也就是说父类实现了请求传递的功能,子类实现请求的处理,符合单一职责原则,各个实现类只完成一个动作或逻辑,也就是只有一个原因引起类的改变,我建议大家在使用的时候用这种方法,好处是非常明显的了,子类的实现非常简单,责任链的建立也是非常灵活的。 责任链模式屏蔽了请求的处理过程,你发起一个请求到底是谁处理的,这个你不用关心,只要你把请求抛给责任链的第一个处理者,最终会返回一个处理结果(当然也可以不做任何处理),作为请求者可以不用知道到底是需要谁来处理的,这是责任链模式的核心,同时责任链模式也可以作为一种补救模式来使用。举个简单例子,如项目开发的时候,需求确认是这样的:一个请求(如银行客户存款的币种),一个处理者(只处理人民币),但是随着业务的发展(改革开放了嘛,还要处理美元、日元等),处理者的数量和类型都有所增加,那这时候就可以在第一个处理者后面建立一个链,也就是责任链来处理请求,如果是人民币,好,还是第一个业务逻辑来处理;如果是美元,好,传递到第二个业务逻辑来处理;日元、欧元……这些都不用在对原有的业务逻辑产生很大改变,通过扩展实现类就可以很好地解决这些需求变更的问题。(这个位置还不如用策略模式呢 适配器模式等)
3.5 模板方法
3.6 策略
3.7 代理模式🌟🌟🌟🌟🌟
1最简单的代理
最简单的代理模式,就是定义被代理对象的接口类;然后具体被代理对象和代理对象都实现这个接口,并且再代理对象类中关联一个被代理对象
1关联 聚合等概念区别
2静态代理代码示例
代理对象中有一个 被代理对象类型的 成员变量; 这就是关联关系(聚合也是这种形式,只是语义上跟关联不一样)
3 代理模式的意义
代理模式是的 真实角色(具体被代理对象)的 职责更清晰,只需要关注本职事务, 针对不同角色的不同处理啊,前置后置处理啊统统交给代理的对象去做; 面向切面成就此引出了!!!
2 动态代理 (强制代理)
1 静态代理 对比 动态代理
普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的,这样的解释还是比较复杂,我们还是用实例来讲解
普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的。我们以上面的例子作为扩展,我自己作为一个游戏玩家,我肯定自己不练级了,也就是场景类不能再直接new一个GamePlayer对象了,它必须由GamePlayerProxy来进行模拟场景, 类图修改如图12-4所示
强制代理在设计模式中比较另类,为什么这么说呢?一般的思维都是通过代理找到真实的角色,但是强制代理却是要“强制”,你必须通过真实角色查找到代理角色,否则你不能访问。甭管你是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色。这么说吧,高层模块new了一个真实角色的对象,返回的却是代理角色,这就好比是你和一个明星比较熟,相互认识,有件事情你需要向她确认一下,于是你就直接拨通了明星的电话:
“喂,沙比呀,我要见一下×××导演,你帮下忙了!”
“不行呀衰哥,我这几天很忙呀,你找我的经纪人吧……”
在接口上增加了一个getProxy方法,真实角色GamePlayer可以指定一个自己的代理,除了代理外谁都不能访问。我们来看代码,先看IGamePlayer接口,如代码清单12-13所示。
package com.binc.testspring.study.proxy;
interface IGamePayer2 {
public void Login(String name, String password);
public void killBoss();
public void upgrade();
public IGamePayer2 getProxy();
}
/**
* FileName: Test1 这种格式是从使用端来说只能看到代理对象,看不到具体被代理对象了; 算是一种高度封装,这就是普通代理
* 还有另一种,:强制代理, 就是能看到被代理的对象,看不到代理对象,但是操作的时候却是用的代理对象执行的; 详见test2
* Autho: binC
* Date: 2022/4/24 14:04
*/
public class Test2 {
public static void main(String[] args) {
IGamePayer2 gg = new GamePlayer2("zhangsn");
System.out.println("***********************");
gg.Login("123","12123");
gg.killBoss();
gg.upgrade();
IGamePayer2 gamePayer2 = new GamePlayer2("zhangsn");
IGamePayer2 proxy = new GamePlayerProxy2(gamePayer2);
System.out.println("-----------------");
proxy.Login("张山", "123456");
proxy.killBoss();
proxy.upgrade();
// 从这里可以看到,用的时候完全你看不到有代理对象存在,但是如果用上边那种方式调用的话,就不是用代理对象了,然后就会执行的别的分支上去; 只有通过我们真是对象去获得他的代理对象,然后在去执行才行;
IGamePayer2 gp = new GamePlayer2("zhangsn");
IGamePayer2 proxy1 = gp.getProxy();// 强代理; 建议用这个
System.out.println("==============");
proxy1.Login("张山", "123456");
proxy1.killBoss();
proxy1.upgrade();
/*
***********************
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
-----------------
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
==============
登录名为张山的用户登录成功
杀怪
升级
Process finished with exit code 0
*/
}
}
/*
被代理对象
*/
class GamePlayer2 implements IGamePayer2 {
private String name;
//我本身是被代理对象但是我这里放一个我的代理对象的引用
private IGamePayer2 proxy = null;
public GamePlayer2(String name) {
this.name = name;
}
/*
找到自己的代理
*/
@Override
public IGamePayer2 getProxy() {
this.proxy = new GamePlayerProxy2(this);
return this.proxy;
}
private boolean isProxy() {
return (this.proxy != null);
}
@Override
public void Login(String name, String password) {
if (this.isProxy()) {
System.out.println("登录名为" + name + "的用户登录成功");
} else {
System.out.println("请使用指定的代理访问");
}
}
@Override
public void killBoss() {
if (this.isProxy()) {
System.out.println("杀怪");
} else {
System.out.println("请使用指定的代理访问");
}
}
@Override
public void upgrade() {
if (this.isProxy()) {
System.out.println("升级");
} else {
System.out.println("请使用指定的代理访问");
}
}
}
/*
代理对象
*/
class GamePlayerProxy2 implements IGamePayer2 {
private IGamePayer2 gamePayer = null;
public GamePlayerProxy2(IGamePayer2 gamePayer2) {
this.gamePayer = gamePayer2;
}
@Override
public void Login(String name, String password) {
this.gamePayer.Login(name, password);
}
@Override
public void killBoss() {
this.gamePayer.killBoss();
}
@Override
public void upgrade() {
this.gamePayer.upgrade();
}
@Override
public IGamePayer2 getProxy() {
return null;
}
}
从这里可以看到,用的时候完全你看不到有代理对象存在,但是如果用上边那种方式调用的话,就不是用代理对象了,然后就会执行的别的分支上去; 只有通过我们真是对象去获得他的代理对象,然后在去执行才行;
一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,它应该在代理的接口中定义
增加了一个IProxy接口,其作用是计算代理的费用
上边讲到的都是引子,真正好用的就是最简单的静态代理和之后要讲的动态代理;什么是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的方式就是静态代理。本章节的核心部分就在动态代理上,现在有一个非常流行的名称叫做面向横切面编程,也就是AOP(AspectOriented Programming),其核心就是采用了动态代理机制,既然这么重要,我们就来看看动态代理是如何实现的,还是以打游戏为例,类图修改一下以实现动态代理,如图12-7所示。
在类图中增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。我们来看程序,接口保持不变,实现类也没有变化,请参考代码清单12-1和代码清单12-2所示。我们来看DynamicProxy类,如代码清单12-21所示