目录
1、建造者模式
产品,省略get、set方法
class Computer {
private String cpu;
private String gpu;
private String memory;
private String hd;
}
建造者:
当需要扩展时,只需继承ComputerBuilder 接口,并实现相应方法。
interface ComputerBuilder {
void setCpu();
void setGpu();
void seMemery();
void setHd();
Computer build();
}
// 高品质电脑
class AdvancedComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void setCpu() {
computer.setCpu("i7 9700K");
}
@Override
public void setGpu() {
computer.setGpu("GTX3090");
}
@Override
public void seMemery() {
computer.setMemory("32G");
}
@Override
public void setHd() {
computer.setHd("500G固态+2T机械");
}
public Computer build() {
return computer;
}
}
指挥者:
class Director {
public Computer build(ComputerBuilder cb) {
cb.setCpu();
cb.setGpu();
cb.seMemery();
cb.setHd();
return cb.build();
}
}
调用:
public class Test {
public static void main(String[] args) {
AdvancedComputerBuilder acb = new AdvancedComputerBuilder();
Director director = new Director();
Computer computer = director.build(acb);
System.out.println(computer);
}
}
优点:
1)创建对象的过程稳定不变的(因为有ComputerBuilder接口来稳定过程)
2)创建对象的过程只写了一次,没有重复代码(由指挥者完成)
3)当需要扩展指挥者的时候,不用修改之前的代码
2、装饰器模式
业务需求:
业务场景:卖咖啡(四种咖啡) Decaf Espresso DrakRoast HouseBlend 因为所有咖啡都有共性,因为把共性提到一个父类:Beverage
abstract class Beverage {
private String description;
public Beverage(String description) {
this.description = description;
}
public abstract double cost();
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
class Decaf extends Beverage {
public Decaf() {
super("无咖啡因咖啡");
}
@Override
public double cost() {
return 1;
}
}
class Espresso extends Beverage {
public Espresso() {
super("浓缩咖啡");
}
@Override
public double cost() {
return 2;
}
}
class DrakRoast extends Beverage {
public DrakRoast() {
super("焦炒咖啡");
}
@Override
public double cost() {
return 1.5;
}
}
class HouseBlend extends Beverage {
public HouseBlend() {
super("混合咖啡");
}
@Override
public double cost() {
return 3;
}
}
问题分析:
如果要在咖啡中填加牛奶、摩卡等时,按这种设计就需要添加相应的类:
为Decaf加牛奶:class DecafWithMilk
为Espresso加牛奶:class Espresso
......
这样添加下去就会导致类大量增加,这就无法适应新的扩展。
采用装饰器模式:
// 装饰器
abstract class Condiment extends Beverage {
protected Beverage beverage;
public Condiment(Beverage beverage) {
super("调料");
this.beverage = beverage;
}
}
class Milk extends Condiment {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.2;
}
@Override
public String getDescription() {
String msg = beverage.getDescription() + " 牛奶";
return msg;
}
}
class Soy extends Condiment {
public Soy(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.1;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 豆浆";
}
}
UML类图:
优点:
1)扩展功能时,不会违背开闭原则,只需增加一个装饰器实现就行。
缺点:
1)装饰器模式虽然减少了类数量,但仍然会增加很多具体的小类,导致不够直观清晰
2)使用时可能需要更多的对象来表示继承关系中的一个对象(如:上述例子需要添加牛奶、豆浆,需要这两个对象都要实现)
3)多层装饰比较复杂,如排查问题不宜发现问题所在
3、模板设计模式
模板模式在一个方法中定义一个业务的骨架,其中一些具体逻辑实现由子类进行实现。
非模板模式代码:
public static void main(String[] args) {
System.out.println("程序执行开始。。。");
long startTime = System.currentTimeMillis();
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 1000000; i++) {
linkedList.add(0, i);
}
long endTime = System.currentTimeMillis();
System.out.println("程序执行用时:" + (endTime - startTime) + "ms");
System.out.println("程序执行结束。。。");
}
模板模式:
abstract class Template {
public void template() {
System.out.println("程序执行开始。。。");
long startTime = System.currentTimeMillis();
code();
long endTime = System.currentTimeMillis();
System.out.println("程序执行用时:" + (endTime - startTime) + "ms");
System.out.println("程序执行结束。。。");
}
protected abstract void code();
}
class TestA extends Template {
@Override
protected void code() {
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 1000000; i++) {
linkedList.add(0, i);
}
}
}
4、适配器模式
一个类的接口转换成客户希望的另一个接口。是配置模式让那些不兼容的类可以在一起工作。
设置一个简单的业务逻辑来讲解适配器模式:
class Calc {
public int add(int a, int b) {
return a + b;
}
}
此类用来求两个数的和,这时需求修改,需要求三个数的和。使用适配器模式:
class Calc {
public int add(int a, int b) {
return a + b;
}
}
// 适配器
class CalcAdapter {
private Calc calc;
public CalcAdapter(Calc c) {
calc = c;
}
public int add(int a, int b, int c) {
return calc.add(calc.add(a, b), c);
}
}
public class Test {
public static void main(String[] args) {
Calc c = new Calc();
CalcAdapter calcAdapter = new CalcAdapter(c);
calcAdapter.add(1, 2, 3);
}
}
5、策略模式
例:
abstract class Duck {
public void quack() {
System.out.println("叫两声");
}
public void swim() {
System.out.println("游泳");
}
public abstract void display();
}
class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("绿头鸭子");
}
}
class RubberDuck extends Duck {
@Override
public void quack() {
System.out.println("捏两下才叫");
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
}
若需要增加一个fly方法,最简单方式就是在Duck添加一个fly方法,但这样就会导致Duck的子类都具备了fly方法,但橡皮鸭RubberDuck子类不应有该方法,故这样直接设计有问题。
针对上述问题进行优化:
interface Flyable {
void fly();
}
interface Quackable{
void quack();
}
abstract class Duck {
public void swim() {
System.out.println("游泳");
}
public abstract void display();
}
// 绿头鸭会叫并会飞
class MallardDuck extends Duck implements Flyable, Quackable {
@Override
public void display() {
System.out.println("绿头鸭子");
}
@Override
public void fly() {
System.out.println("会飞");
}
@Override
public void quack() {
System.out.println("叫两声");
}
}
// 橡皮鸭被捏才会叫且不会飞
class RubberDuck extends Duck implements Quackable {
@Override
public void quack() {
System.out.println("捏两下才叫");
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
}
将fly与quack设计为接口,需要实现该方法时再实现该接口。但仍然有个问题,新加一个鸭子角色时都要进行判断是否具有某些功能,且代码没有重复性可言,比如都有fly方法那就要都实现fly,导致这类代码过于臃肿。
采用策略模式,实现运行时功能修改:
// 飞动作的接口
interface FlyBehavior {
void fly();
}
interface QuackBehavior{
void quack();
}
// 实现类
class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("用翅膀飞");
}
}
class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞");
}
}
class FlyWithKick implements FlyBehavior {
@Override
public void fly() {
System.out.println("踢飞");
}
}
class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("嘎嘎叫");
}
}
// 鸭子抽象类
abstract class Duck {
protected FlyBehavior fb;
protected QuackBehavior qb;
public FlyBehavior getFb() {
return fb;
}
public void setFb(FlyBehavior fb) {
this.fb = fb;
}
public QuackBehavior getQb() {
return qb;
}
public void setQb(QuackBehavior qb) {
this.qb = qb;
}
public void swim() {
System.out.println("游泳");
}
public void performFly() {
fb.fly();
}
public void performQuack() {
qb.quack();
}
public abstract void display();
}
// 绿头鸭会叫并会飞
class MallardDuck extends Duck {
public MallardDuck() {
this.fb = new FlyNoWay();
this.qb = new Quack();
}
@Override
public void display() {
System.out.println("绿头鸭子");
}
}
// 橡皮鸭被捏才会叫且不会飞
class RubberDuck extends Duck {
public RubberDuck() {
this.fb = new FlyWithWings();
this.qb = new Quack();
}
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
}
public class Test {
public static void main(String[] args) {
RubberDuck rd = new RubberDuck();
rd.performQuack();
rd.swim();
rd.display();
rd.performFly();
// 运行时修改飞行动作
rd.setFb(new FlyWithKick());
rd.performFly();
}
}
6、代理模式
动态代理简单案例:
interface ICalc {
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
}
class CalcImpl implements ICalc {
@Override
public int add(int a, int b) {
System.out.println("+");
return a + b;
}
@Override
public int sub(int a, int b) {
System.out.println("-");
return a - b;
}
@Override
public int mul(int a, int b) {
System.out.println("*");
return a * b;
}
@Override
public int div(int a, int b) {
System.out.println("/");
return a / b;
}
}
class MyHandler implements InvocationHandler {
private Object target;
public MyHandler(Object target) {
this.target = target;
}
/**
* 动态代理执行的方法
* @param proxy 动态代理的对象
* @param method 调用的接口方法
* @param args 调用的接口方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " 开始,参数是" + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println(method.getName() + " 结束,结果是" + result);
return 0;
}
}
public class Test {
public static void main(String[] args) {
ClassLoader cl = Test.class.getClassLoader();
ICalc proxy = (ICalc) Proxy.newProxyInstance(cl, new Class[]{ICalc.class}, new MyHandler(new CalcImpl()));
proxy.add(1, 2);
}
}
7、原型模式
使用原型模式:
- 必须让目标类实现Cloneable接口,改接口没有任何抽象方法,仅仅是一个标记接口。表示实现该接口的类可以被克隆必须重写
- java.long.Object类的clone方法,将返回权限改为public
class WeekReport implements Cloneable, Serializable {
private int id;
private String emp;
private String summary;
private String plain;
private String suggestion;
private Date time;
...
get、set方法
...
toString方法
...
@Override
public Object clone() throws CloneNotSupportedException {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(this);
oos.close();
// 从内存中读取数据
byte[] bytes = out.toByteArray();
InputStream in = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(in);
Object clone = ois.readObject();
ois.close();
return clone;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
8、组合模式
要求:以层级的方式显示各个菜单项,并能满足功能的扩展。
// 菜单项
class MenuItem {
private String name;
private String description;
public MenuItem(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void print(String prefix) {
System.out.println(prefix + getName() + ":" + getDescription());
}
}
// 菜单
class Menu {
private List<MenuItem> list;
private String name;
private String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void add(MenuItem mc) {
list.add(mc);
}
public void print(String prefix) {
System.out.println(prefix + " (" + getName() + ") " + getDescription());
Iterator<MenuItem> iterator = list.iterator();
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
menuItem.print("\t" + prefix);
}
}
}
上述代码存在问题:
- 代码冗余,重复代码太多
- 该菜单只能添加菜单子项不能添加菜单,无法做到二级及以上菜单
- 无法显示层级,没解耦功能扩展代价太大
通过抽取重复项以及递归方式优化代码:
// 菜单组件,用户抽取菜单和菜单单项的共性
abstract class MenuComponent {
private String name;
private String description;
public MenuComponent(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public abstract void print(String prefix);
}
// 菜单项
class MenuItem extends MenuComponent {
public MenuItem(String name, String description) {
super(name, description);
}
public void print(String prefix) {
System.out.println(prefix + getName() + ":" + getDescription());
}
}
// 菜单
class Menu extends MenuComponent {
private List<MenuComponent> list;
public Menu(String name, String description) {
super(name, description);
list = new ArrayList<>();
}
public void add(MenuComponent mc) {
list.add(mc);
}
public void print(String prefix) {
System.out.println(prefix + " (" + getName() + ") " + getDescription());
Iterator<MenuComponent> iterator = list.iterator();
while (iterator.hasNext()) {
MenuComponent menuComponent = iterator.next();
menuComponent.print("\t" + prefix);
}
}
}
分析:看似完成了代码的抽取以及层级的显示,但这时需求来了,需要添加一个功能,增加价格以及该菜品是否为荤素。如果直接在源代码中修改就违反了开闭原则,故这种设计也存在缺陷。(注:不要说在设计之前就把这些需求封装好,没有人能把所有情况考虑完毕,现在是加价格,下次如果加甜辣程度呢?故这种修改只能在客户端也就是用户这边进行修改)
解决方案:
// 为保证菜单和菜单项具有相同接口,在它们的父类这里把所需的方法都定义出来
abstract class MenuComponent {
private String name;
private String description;
public MenuComponent(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public abstract void print(String prefix);
public List getList() {
throw new UnsupportedOperationException();
}
// 这些方法对于菜单和菜单项而言都只有一些有意义的,为什么非要定义在父类中?
// 为了:组合模式使得用户对单个对象和组合对象的使用具有一致性
// 属于菜单的方法
public void add(MenuComponent mc) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent mc) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
throw new UnsupportedOperationException();
}
// 属于菜单项的方法
public double getPrice() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}
public CompositeIterator iterator() {
throw new UnsupportedOperationException();
}
}
// 菜单项
class MenuItem extends MenuComponent {
private boolean vegetarian;
private double price;
@Override
public boolean isVegetarian() {
return vegetarian;
}
public void setVegetarian(boolean vegetarian) {
this.vegetarian = vegetarian;
}
@Override
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public MenuItem(String name, String description, boolean vegetarian, double price) {
super(name, description);
this.vegetarian = vegetarian;
this.price = price;
}
public void print(String prefix) {
String str = vegetarian ? "(素食)" : "";
System.out.println(prefix + getName() + str + ":" + getDescription());
}
}
// 菜单
class Menu extends MenuComponent {
private List<MenuComponent> list;
public Menu(String name, String description) {
super(name, description);
list = new ArrayList<>();
}
public List<MenuComponent> getList() {
return list;
}
public void add(MenuComponent mc) {
list.add(mc);
}
@Override
public void remove(MenuComponent mc) {
list.remove(mc);
}
@Override
public MenuComponent getChild(int i) {
return list.get(i);
}
public void print(String prefix) {
System.out.println(prefix + " (" + getName() + ") " + getDescription());
Iterator<MenuComponent> iterator = list.iterator();
while (iterator.hasNext()) {
MenuComponent menuComponent = iterator.next();
menuComponent.print("\t" + prefix);
}
}
public CompositeIterator iterator() {
return new CompositeIterator(list.iterator());
}
}
// 迭代器模式
class CompositeIterator implements Iterator<MenuComponent> {
private Stack<Iterator<MenuComponent>> s = new Stack<>();
public CompositeIterator(Iterator<MenuComponent> it) {
s.push(it);
}
@Override
public boolean hasNext() {
if (s.isEmpty()) {
return false;
} else {
Iterator<MenuComponent> it = s.peek();
if (!it.hasNext()) {
s.pop();
return hasNext();
} else {
return true;
}
}
}
@Override
public MenuComponent next() {
Iterator<MenuComponent> it = s.peek();
MenuComponent mc = it.next();
if (mc instanceof Menu) {
s.push(((Menu) mc).getList().iterator());
}
return mc;
}
}
9、观察者模式
业务场景设置:
* 一个游戏中有一角色,角色拥有HP、MP,在游戏窗口中,有一些面板用来显示该游戏角色的hp、mp
* 实现面板有:角色头顶的血条、数值显示面吧、HP和MP的显示球
(1)简单粗暴的代码实现
class Role {
private Integer hp;
private Integer mp;
private String name;
public Integer getHp() {
return hp;
}
public void setHp(Integer hp) {
this.hp = hp;
// 当hp发生变化时,一定要“通知”3个面板地方,在这里用输出来模拟
System.out.println("头顶血条更新hp为:" + hp);
System.out.println("球形血量更新hp为:" + hp);
System.out.println("面板数值更新hp为:" + hp);
}
public Integer getMp() {
return mp;
}
public void setMp(Integer mp) {
this.mp = mp;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 模拟怪物
class Monster {
// 怪物攻击
public void attack(Role r, int attackValue) {
r.setHp(r.getHp() - attackValue);
}
}
这时变化来了,业务变更需要一个角色受到伤害时弹出一个伤害数值。只根据上诉代码时,那么只能修改原始代码,这样就违反了开闭原则,不应该这样设计。而且这样设计也违反了单一职责。
针对上诉问题进行代码优化:
// 角色
class Role {
private Integer hp;
private Integer mp;
private String name;
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer obj) {
observers.add(obj);
}
public void removeObserver(Observer obj) {
observers.remove(obj);
}
public void notifyObservers() {
for (Observer obj : observers) {
obj.update(hp);
}
}
public Integer getHp() {
return hp;
}
public void setHp(Integer hp) {
this.hp = hp;
// 每当hp变化就通知面板
notifyObservers();
}
public Integer getMp() {
return mp;
}
public void setMp(Integer mp) {
this.mp = mp;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 面板的接口
interface Observer {
// 一个方法,来接收主体发送的数据
public void update(int hp);
}
// 数值面板
class Panel implements Observer {
@Override
public void update(int hp) {
System.out.println("在左上角的数值面板,更新数据" + hp);
}
}
// 球形面板
class BallPanel implements Observer {
@Override
public void update(int hp) {
System.out.println("在球形面板中,更新数据" + hp);
}
}
// 头顶血条
class HeadPanel implements Observer {
@Override
public void update(int hp) {
System.out.println("在角色头顶血条上,更新数据" + hp);
}
}
// 模拟怪物
class Monster {
// 怪物攻击
public void attack(Role r, int attackValue) {
r.setHp(r.getHp() - attackValue);
}
}
// -----------------客户端自定义面板--------------------------
// 用于测试后续新增功能
class PopupPanel implements Observer {
@Override
public void update(int hp) {
System.out.println("弹出面板,更新数据:" + hp);
}
}
优点:
- 当新增一个面板显示数据时,不会违反开闭原则。
- 因为每一个面板的算法,都被隔离在不同的类,也不违反单一职责。
缺点:
目前主体只是将hp的修改广播给所有的观察者,但要新增一个mp时,则必须要修改原始代码,还是违反了开闭原则
再进一步优化,这里不再更新时传数据,而是在具体的面板的连接角色对象,让角色与面板双向引用。
interface Observer {
// 一个方法,来接收主体发送的数据
public void update();
}
// 数值面板
class Panel implements Observer {
// 连接角色
private Role r;
public Panel(Role r) {
this.r = r;
}
@Override
public void update() {
System.out.println("在左上角的数值面板,更新数据" + r);
}
}
// 球形面板
class BallPanel implements Observer {
private Role r;
public BallPanel(Role r) {
this.r = r;
}
@Override
public void update() {
System.out.println("在球形面板中,更新数据" + r);
}
}
// 头顶血条
class HeadPanel implements Observer {
private Role r;
public HeadPanel(Role r) {
this.r = r;
}
@Override
public void update() {
System.out.println("在角色头顶血条上,更新数据" + r);
}
}
测试类:
public static void main(String[] args) {
Role r = new Role();
r.setName("张三");
r.setHp(100);
r.setMp(100);
Panel p = new Panel(r);
BallPanel b = new BallPanel(r);
HeadPanel h = new HeadPanel(r);
r.addObserver(p);
r.addObserver(b);
r.addObserver(h);
// 受到怪物攻击后 -3 hp
Monster m = new Monster();
m.attack(r, 3);
}
分析上述优化代码可知,主体中的涉及观察者的方法可能共用,因而可以抽取出来。其代码为:
interface Subject {
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
然后在各个主体(本例为Role)中实现该接口。