一、适配器模式
用于处理遗留代码,将已有的代码适配新接口,或包装到一些新的接口中。
目的:将现有的旧接口转换成新的客户端接口,尽可能多的重用原来的已经通过测试的代码,并且可以对新接口自由修改(将一个接口转换为客户端所期待的接口,从而使两个接口不兼容的类可以在一起工作)。
它还有一个别名叫Wrapper(包装器),就是将目标类用一个新类包装一下,加一层。
角色:
Adapter:将调用转发给Adaptee的适配器类
Adaptee:需要适配的旧代码
target:支持的新接口
示例:
原先有一个播放器Mp4Player,只能播放Mp4格式音乐,现在多了一个Mp3格式的音乐播放器AudioPlayer,要求一个接口适配两个播放器
之前Mp4Player抽象的接口:
public interface AdvancedMediaPlayer {
public void playVlc(String fileName); //别的播放格式
public void playMp4(String fileName);
}
Mp4Player实现:
public class Mp4Player implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
//什么也不做
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
新的adapter:
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc") ){
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}
新类:
public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("Playing mp3 file. Name: "+ fileName);
}
//mediaAdapter 提供了播放其他文件格式的支持
else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
}
else{
System.out.println("Invalid media. "+ audioType + " format not supported");
}
}
}
客户端使用:
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
二、代理模式
为其他对象提供一种代理以控制对这个对象的访问
1.静态代理
举例:打官司时的代理律师:
//诉讼接口
public interface ILawSuit {
void submit(String proof);//提起诉讼
void defend();//法庭辩护
}
//原告类
public class YuanGao implements ILawSuit {
@Override
public void submit(String proof) {
System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
}
@Override
public void defend() {
System.out.println(String.format("铁证如山,%s还钱","老板"));
}
}
//代理律师类
public class ProxyLawyer implements ILawSuit {
ILawSuit plaintiff;//持有要代理的那个对象
public ProxyLawyer(ILawSuit plaintiff) {
this.plaintiff=plaintiff;
}
@Override
public void submit(String proof) {
//增加审核材料等操作
plaintiff.submit(proof);
}
@Override
public void defend() {
//增加辩护语言优化等
plaintiff.defend();
}
}
//产生代理对象的静态代理工厂类
public class ProxyFactory {
public static ILawSuit getProxy(){
return new ProxyLawyer(new YuanGao());
}
}
//客户端:
public static void main(String[] args) {
ProxyFactory.getProxy().submit("工资流水在此");
ProxyFactory.getProxy().defend();
}
2.动态代理
灵活确定使用哪个代理
有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作的,而Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。
//构建一个动态代理类,实现了InvocationHandler接口,重写了invoke方法
public class DynProxyLawyer implements InvocationHandler {
private Object target;//被代理的对象
public DynProxyLawyer(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("案件进展:"+method.getName());
Object result=method.invoke(target,args);
return result;
}
}
//修改代理工厂方法为
public class ProxyFactory {
...
public static Object getDynProxy(Object target) {
InvocationHandler handler = new DynProxyLawyer(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
}
}
//客户端
public static void main(String[] args) {
ILawSuit proxy= (ILawSuit) ProxyFactory.getDynProxy(new YuanGao());
proxy.submit("工资流水在此");
proxy.defend();
}
//结果:(每个方法的入口从invoke开始执行)
案件进展:submit
老板欠薪跑路,证据如下:工资流水在此
案件进展:defend
铁证如山,老板还钱
三、装饰器模式
装饰模式是在不必改变原类和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象
使用场景:
需要在运行时动态的给一个对象增加额外的职责时候
需要给一个现有的类增加职责,但是又不想通过继承的方式来实现的时候(应该优先使用组合而非继承),或者通过继承的方式不现实的时候(可能由于排列组合产生类爆炸的问题)。
举例:
假设我们有一个原味咖啡的类 OriginalCoffee, 目前的需求就是要动态的给这个类的一些实例增加一些额外功能,此处就是动态的对某些咖啡制作过程增加新的流程,例如加奶,加糖,而有的咖啡却保持原味不变。
//先声明原始对象的接口
public interface ICoffee {
void makeCoffee();
}
//构建原始对象
public class OriginalCoffee implements ICoffee {
@Override
public void makeCoffee() {
System.out.print("原味咖啡 ");
}
}
//定义抽象装饰器接口,需要实现原始对象的接口
public abstract class CoffeeDecorator implements ICoffee {
private ICoffee coffee;
public CoffeeDecorator(ICoffee coffee){
this.coffee=coffee;
}
@Override
public void makeCoffee() {
coffee.makeCoffee();
}
}
//构建两种装饰类
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addMilk();
}
private void addMilk(){
System.out.print("加奶 ");
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addSugar();
}
private void addSugar(){
System.out.print("加糖");
}
}
//客户端
public static void main(String[] args) {
//原味咖啡
ICoffee coffee=new OriginalCoffee();
coffee.makeCoffee();
System.out.println("");
//加奶的咖啡
coffee=new MilkDecorator(coffee);
coffee.makeCoffee();
System.out.println("");
//先加奶后加糖的咖啡
coffee=new SugarDecorator(coffee);
coffee.makeCoffee();
}
可以从客户端调用代码看出,装饰者模式的精髓在于动态的给对象增减功能
装饰器模式和代理模式的区别:一般认为代理模式侧重于使用代理类增强对被代理对象的访问,而装饰器模式侧重于对被装饰对象的功能增减;另外,装饰器模式主要是提供一组装饰者类,然后形成一个装饰器栈,来动态的对某一个对象不断加强,而代理一般不会使用多级代理
四、桥接模式
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。核心要义:桥是用来将两个独立的结构联系起来,而这两个被联系起来的结构可以独立的变化
角色:
抽象化(Abstraction)接口:抽象化给出的定义,并保存一个对实现化对象的引用。
修正抽象化(RefinedAbstraction)类:扩展抽象化角色,改变和修正父类对抽象化的定义。
实现化(Implementor)接口:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
具体实现化(ConcreteImplementor)类:这个角色给出实现化角色接口的具体实现。
适用于排列组合的场景:一杯咖啡,选中杯、大杯、超大杯,以及加不加糖,不能写六个类去实现ICoffee接口,独立实现makeCoffee接口,因为后来可能又来“加不加奶”的需求,应该用桥接模式去实现,这里有两个变化维度,咖啡的容量和口味,而且都需要独立变化,可以将容量理解为抽象部分,而口味理解为实现部分,这两个部分需要桥接。
//实现化接口
public interface ICoffeeAdditives {
void addSomething();
}
//抽象化
public abstract class Coffee {
//持有ICoffeeAdditives的引用
protected ICoffeeAdditives additives;
//构造函数注入,就是桥接过程
public Coffee(ICoffeeAdditives additives){
this.additives=additives;
}
public abstract void orderCoffee(int count);
}
//抽象化的修正类
public abstract class RefinedCoffee extends Coffee {
public RefinedCoffee(ICoffeeAdditives additives) {
super(additives);
}
public void checkQuality(){
Random ran=new Random();
System.out.println(String.format("%s 添加%s",additives.getClass().getSimpleName(),ran.nextBoolean()?"太多":"正常"));
}
}
//实现化具体类
//加奶
public class Milk implements ICoffeeAdditives {
@Override
public void addSomething() {
System.out.println("加奶");
}
}
//加糖
public class Sugar implements ICoffeeAdditives {
@Override
public void addSomething() {
System.out.println("加糖");
}
}
//实现抽象化部分的接口
//大杯
public class LargeCoffee extends RefinedCoffee {
public LargeCoffee(ICoffeeAdditives additives) {
super(additives);
}
@Override
public void orderCoffee(int count) {
additives.addSomething();
System.out.println(String.format("大杯咖啡%d杯",count));
}
}
//小杯
public class SmallCoffee extends RefinedCoffee {
public LargeCoffee(ICoffeeAdditives additives) {
super(additives);
}
@Override
public void orderCoffee(int count) {
additives.addSomething();
System.out.println(String.format("小杯咖啡%d杯",count));
}
}
//客户端
public static void main(String[] args) {
//点两杯加奶的大杯咖啡
RefinedCoffee largeWithMilk=new LargeCoffee(new Milk());
largeWithMilk.orderCoffee(2);
largeWithMilk.checkQuality();
}
五、组合模式
组合模式允许以相同的方式处理单个对象和对象的组合体,在组合体内部,一般用数组、树、图、链表等数据结构来组合对象
应用场景:
当你的程序结构有类似树一样的层级关系时,例如文件系统,视图树,公司组织架构等等
当你要以统一的方式操作单个对象和由这些对象组成的组合对象的时候。
角色:
Component:抽象节点,定义统一的处理操作
Leaf:叶子结点,即单个对象
composite:复合节点,组合对象,里面持有一个List<Component>
举例:
//定义抽象节点,统一处理接口
public abstract class OrganizationComponent {
private String name;
public OrganizationComponent(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract void add(OrganizationComponent organization);
public abstract OrganizationComponent getChild(String orgName);
public abstract int getStaffCount();
@Override
public String toString() {
return name;
}
}
//组合类
public class OrganizationComposite extends OrganizationComponent {
//很关键,这体现了组合的思想
private List<OrganizationComponent> organizations = new ArrayList<>();
public OrganizationComposite(String name) {
super(name);
}
@Override
public void add(OrganizationComponent organization) {
organizations.add(organization);
}
@Override
public OrganizationComponent getChild(String orgName) {
for (OrganizationComponent org : organizations) {
OrganizationComponent targetOrg = org.getChild(orgName);
if (targetOrg != null) {
return targetOrg;
}
}
return null;
}
@Override
public int getStaffCount() {
int count = 0;
for (OrganizationComponent organization : organizations) {
count += organization.getStaffCount();
}
return count;
}
}
//叶子结点
public class ItDepartment extends OrganizationComponent {
public ItDepartment(String name) {
super(name);
}
@Override
public int getStaffCount() {
return 20;
}
@Override
public void add(OrganizationComponent organization) {
throw new UnsupportedOperationException(this.getName()+"已经是最基本部门,无法增加下属部门");
}
@Override
public OrganizationComponent getChild(String orgName) {
if(getName().equals(orgName)){
return this;
}
return null;
}
}
// 其他叶子节点类似
...
//客户端
public class CompositeClient {
private OrganizationComponent constructOrganization() {
//构建总部
OrganizationComposite head = new OrganizationComposite("总公司");
AdminDepartment headAdmin = new AdminDepartment("总公司行政部");
ItDepartment headIt = new ItDepartment("总公司It部");
head.add(headAdmin);
head.add(headIt);
//构建分公司
OrganizationComposite branch1 = new OrganizationComposite("天津分公司");
AdminDepartment branch1Admin = new AdminDepartment("天津分公司行政部");
ItDepartment branch1It = new ItDepartment("天津分公司It部");
branch1.add(branch1Admin);
branch1.add(branch1It);
//将分公司加入到head中
head.add(branch1);
return head;
}
public void listOrgInfo() {
OrganizationComponent org = constructOrganization();
System.out.println(String.format("%s共有%d名员工",
org.getName(), org.getStaffCount()));
OrganizationComponent subOrg = org.getChild("天津分公司行政部");
System.out.println(String.format("%s共有%d名员工",
subOrg.getName(), subOrg.getStaffCount()));
}
}
要点:
通用操作定义在Component中,根据使用方式不同,透明方式与安全方式,有一定的不同(透明方式(本例)将所有对外操作都放在Component,叶子节点也得提供这些接口,虽然它实际上不支持这些操作。而安全方式只将叶子节点与组合对象同时提供的操作放在Component)
组合节点Composite不仅要继承Component,而且要持有一个Component的集合
叶子对象只继承Component即可
六、外观模式
对外提供简单的交互接口,隐藏内部的复杂性。将多个子系统的接口统一为一个适配器的接口,对外只暴露适配器的接口,客户端不管子系统内部的联系与实现。两个字:“聚合”
角色:
facade:子系统接口,里面引用各个子模块,然后对外提供统一的接口
subsystem:子系统中定义的类
举例:
//各种子系统
//订单系统
public class OrderSys {
public String getOrderNum(){
System.out.println("获取订单号");
return "123456";
}
}
//支付系统
public class PaymentSys {
private OrderSys orderSys;
public PaymentSys(OrderSys orderSys) {
this.orderSys = orderSys;
}
public BigDecimal getOrderAccount(String orderNum){
System.out.println(String.format("获取%s订单支付金额",orderNum));
return BigDecimal.valueOf(500);
}
}
//物流系统
public class DeliverySys {
public int getDeliveryTime(){
System.out.println("获取配送耗时");
return 30*60;//30分钟
}
}
//外观类
public class ReportFacade {
public void generateReport() {
OrderSys orderSys = new OrderSys();
PaymentSys paymentSys = new PaymentSys(orderSys);
DeliverySys deliverySys = new DeliverySys();
final String orderNum = orderSys.getOrderNum();
System.out.println(String.format("\n报表\n--------------------------------------------\n" +
"订单号:%s | 金额:%s元 | 配送耗时:%s分钟",
orderNum,
paymentSys.getOrderAccount(orderNum).toPlainString(),
String.valueOf(deliverySys.getDeliveryTime() / 60))
);
}
}
//客户端,其实就是后端封装一层,做了查询各种数据的事,不用前端挨个接口调用
public class FacadeClient {
public void printReport(){
new ReportFacade().generateReport();
}
}
七、享元模式
允许使用对象共享来有效地支持大量细粒度对象,利用对象池来达到加速的目的,例如,Boolean.valueOf()只有true、false两个值,不用构造新对象,直接从对象池里拿即可。跟创建者模式的区别在于,创建者模式是保存的可变域对象的容器,享元模式保存的是不可变域对象
角色:
FlyweightFactory: 负责给客户端提供共享对象
Flyweight:享元接口,定义所有对象共享的操作
ConcreteFlyweight:具体要被共享的类对象,其一般是一个不可变类,内部只保存需要共享的内部状态,它可能不止一个。分为ConcreteShareableFlyweight和ConcreteUnshareableFlyweight,当你不需要共享对象时,但是又需要以统一接口处理此对象,就可以添加ConcreteUnshareableFlyweight这个角色
共享对象的状态分为内部状态与外部状态,内部状态在各个对象间共享,而外部状态由客户端传入,这一点一定要牢记。
举例:实现一个五子棋架构,其中棋子除了位置和颜色不同外,其他都是相同的
//定义一个共享对象通用的接口
public interface Chess {
//绘制棋子
void draw(int x,int y);
}
//将颜色设计为对象的内部状态来共享,而不是外部状态,所以就需要黑白两个对象类。如果你把颜色作为外部状态,那么只需要一个对象类即可。
//黑棋
public class BlackChess implements Chess {
//内部状态,共享
private final Color color = Color.BLACK;
private final String sharp = "圆形";
public Color getColor() {
return color;
}
@Override
public void draw(int x, int y) {
System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlias(), x, y));
}
}
//白棋
public class WhiteChess implements Chess {
//内部状态,共享
private final Color color = Color.WHITE;
private final String sharp = "圆形";
public Color getColor() {
return color;
}
@Override
public void draw(int x, int y) {
System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlias(), x, y));
}
}
//创建享元工厂
public class ChessFactory {
private static final Map<Color, Chess> chessMap = new HashMap<>();
public static Chess getChess(Color color) {
Chess chess = chessMap.get(color);
if (chess == null) {
chess = color == Color.WHITE ? new WhiteChess() : new BlackChess();
chessMap.put(color, chess);
}
return chess;
}
}
//客户端
public class FlyweightClient {
public void playChess() {
//下黑子
Chess backChess1 = ChessFactory.getChess(Color.BLACK);
backChess1.draw(2, 5);
//下白子
Chess whiteChess = ChessFactory.getChess(Color.WHITE);
whiteChess.draw(3, 5);
//下黑子
Chess backChess2 = ChessFactory.getChess(Color.BLACK);
backChess2.draw(2, 6);
System.out.println(String.format("backChess1:%d | backChess2:%d | whiteChess:%d",
backChess1.hashCode(), backChess2.hashCode(), whiteChess.hashCode()));
}
}
可以看到与静态工厂模式很相似,如果元素是不可变域的,以后也不会添加,则适合用享元模式,如果元素可变,后期也许会添加元素,则直接用静态工厂模式