继承、封装、多态
第一、通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。第二、程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
方法重载与重写的区别
重载是发生在同一个类中的同名但参数列表(类型顺序个数)不相同的方法,但是对返回类型没有要求。
而重写是发生在子类覆盖父类方法,其函数名,参数列表,返回值类型都是一样的。
动态参数与重载
动态参数本质上方法的参数是一个数组,动态参数方法可以和固定参数方法重载,但是优先会调用固定参数的方法。
动态参数的一些约定
一般情况下,设计模式可以概括为三大类
创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
大家肯定对单例模式很熟悉了,但是也有一些需要特别注意的点。
1、实现单例模式的懒汉模式和饿汉模式,简单来说就是调用getInstance时创建还是加载类的时候创建。
饿汉模式
public class Singleton1 {
/**
* 私有构造
*/
private Singleton1() {
System.out.println("构造函数Singleton1");
}
/**
* 初始值为实例对象
*/
private static Singleton1 single = new Singleton1();
/**
* 静态工厂方法
* @return 单例对象
*/
public static Singleton1 getInstance() {
System.out.println("getInstance");
return single;
}
}
懒汉模式
public class Singleton1 {
/**
* 私有构造
*/
private c() {
System.out.println("构造函数Singleton1");
}
/**
* 初始值为实例对象
*/
private static Singleton1 single = null;
/**
* 静态工厂方法
* @return 单例对象
*/
public static Singleton1 getInstance() {
if ( single == null){
System.out.println("getInstance");
single = new single();
}
return single;
}
}
2、饿汉模式没啥好说的,主要是懒汉模式在并发情况下有可能会引起对象的多次初始化,这里引入的就是同步锁,对方法或者方法内整个代码段加锁,虽然解决了多次初始化的问题,但是也导致执行效率下降,获取实例的时候必须等其他线程释放了同步锁。
加入同步锁
public class Singleton1 {
......省略
* 静态工厂方法
* @return 单例对象
*/
public static synchronized Singleton1 getInstance() {
if ( single == null){
System.out.println("getInstance");
single = new single();
}
return single;
}
}
3、引入双重锁的概念,双重锁可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。
public static Singleton1 getInstance() {
if (single == null) {
synchronized (Singleton1.class) {
if (single == null) {
single = new Singleton4();
}
}
}
return single;
}
4、但是由于JVM指令重排的关系,导致了双重锁在并发情况下引发一些问题。首先了解一下什么是指令重排。
指令重排为了提高性能,在遵守 as-if-serial 语义(即不管怎么重排序,单线程下程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守。)的情况下,编译器和处理器常常会对指令做重排序。
创建对象过程非原子性,创建对象的过程大致包含下面几个步骤,1:分配内存空间;2:初始化对象;3:将对象的引用,指向分配的内存;
由于在单线程情况下创建对象的过程无论怎么重排,结果都是不变的,所以,创建对象的过程给了JVM重排的机会,这就导致了,有可能分配了内存空间,然后将对象的引用指向了分配的内存,刚好这个时候因为引用不为null,导致了一个没有初始化的对象提供出去了。其他线程在使用这个数据残缺的对象的时候有可能引发一些意想不到的事情。
volatile保证内存可见性,防止指令重排序,并不保证操作原子性。
5、完善后的解决方案
// 禁止指令重排序
private static volatile Singleton INSTANCE = null;
简单工厂模式
简单工厂模式,工厂类就是一个具体类,而且可以生产一个抽象类型所派生出来的具体类对象,但是生产的逻辑是固定的,这就是意味着这个抽象类型有新的类派生出来的时候,如果客户端需要这个工厂去创建新的派生类对象就要重新升级创建方法,不符合开放封闭原则。
public class SimpleFactory {
public static Pizza createPizza(String pizzaType){
Pizza pizza = null;
System.out.println("使用了简单工厂模式");
if (pizzaType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("greek");
} else if (pizzaType.equals("chess")) {
pizza = new ChessPizza();
pizza.setName("chess");
} else if (pizzaType.equals("pepper")) {//新增PepperPizza的时候 修改了源代码 违反了开放封闭原则 如果新增10000个?
//那就很麻烦了
pizza = new PepperPizza();
pizza.setName("pepper");
}
return pizza;
}
}
工厂方式模式
工厂方法模式,实际上会抽象出一个工厂类,抽象出一个产品类,抽象工厂类只提供一个抽象的创建产品的方法,具体的实现交由具体工厂类完成,一个具体的工厂类对应一个具体的产品类,当有新的产品需要生产(扩展),只需要继承抽象产品类,继承抽象工厂类,并且实现生产产品方法即可。
// 抽象工厂
public interface ProductFactory {
Product createProduct();
}
// 抽象产品
public abstract class Product {
private String name;
public abstract void work();
}
// 具体产品
public class TVProduct extends Product{
private String name;
TVProduct(String name){
this.name = name;
}
@Override
public void work() {
System.out.println("My name is " + name +". I can play tv video.");
}
}
// 具体工厂(TV工厂专门为TV产品服务)
public class TVProdctFactory implements ProductFactory{
@Override
public Product createProduct() {
return new TVProduct("TV");
}
}
// 客户端调用
public static void main(String[] args) {
ProductFactory factory = new TVProdctFactory();
Product product = factory.createProduct();
product.work();
}
输出
My name is TV. I can play tv video.
Process finished with exit code 0
很明显,如果有1000个派生产品就会有1000个工厂。
抽象工厂模式
抽象工厂模式与工厂方法模式各有千秋,抽象工厂模式解决的是创建一个对象的组合,也叫产品族。
怎么理解这个产品族呢,可以从一个工厂生产出来的产品就是一个产品族,比如有一个工厂(抽象工厂)他可以加工洋葱,加工肉饼,加工芝士,加工面包但是他不可以忽然自己学会了加工米饭(产品层级),而不同的具体工厂之间的区别只是,A工厂可以生产A芝士,A肉饼和A洋葱(A只是个代称,他可以是A芝士,B芝士,C洋葱),但是他们的任务都是生产加工洋葱、肉饼、芝士、面包(这就是一个产品族)
具体实现如下:
// 抽象工厂类,能够生产的东西是固定的
public interface NormalFactory {
Meat processMeat();
Onion processOnion();
}
// 具体工厂类
public class FRAFactory implements NormalFactory{
@Override
public Meat processMeat() {
return new FRAMeat("franceMeat");
}
@Override
public Onion processOnion() {
return new FRAOnion("franceOnion");
}
}
// 产品
public abstract class Meat {
protected String name;
protected abstract void say();
}
public class FRAMeat extends Meat{
FRAMeat (String name) {
this.name = name;
}
@Override
protected void say() {
System.out.println("I am " + this.name+".");
}
}
public abstract class Onion {
protected String name;
protected abstract void say();
}
public class FRAOnion extends Onion{
FRAOnion(String name){
this.name = name;
}
@Override
protected void say() {
System.out.println("I am " + this.name+".");
}
}
// 客户端
public static void main(String[] args) {
NormalFactory factory = new FRAFactory();
// 这个汉堡店发现法国风味的肉饼和洋葱特别好卖,所以专门有个工厂生产这两个产品
Meat meat = factory.processMeat();
Onion onion = factory.processOnion();
System.out.println("We sell hamburger and who are materials? ");
meat.say();
onion.say();
}
// 运行结果
We sell hamburger and who are materials?
I am franceMeat.
I am franceOnion.
我们可以看到,生产的产品只能从已经确定的产品族中派生,不能重新抽象一个新的产品,否则从抽象工厂至每个具体工厂都要重新编码。如果是已有产品的不同组合,只需要派生产品类以及新增工厂实现抽象工厂即可。
这与工厂方法有什么区别呢?工厂方法针对的是一个产品的生产加工,而抽象工厂针对的是一个产品族的组合生产加工。
如果说工厂模式是专注于生产合种对象而忽略生产对象的细节,那构建者模式就是一个专注于生产对象的细节的一种设计模式,使得一个复杂构建过程的对象,在相同的构建流程,因为构建者的不同可以达到不同的表示(怎么理解最后一句话,请看官继续往下看)。
虽然是初学这个模式,但是我觉得这篇文章写得挺不错。
链接: 秒懂设计模式之构建者模式
概念
Product:最终要生产的产品
Builder:构建者的抽象基类,其定义了构建Product的抽象步骤,其实体类需要实现这些步骤,并且包含了返回产品的方法。
ConcreteBuilder:构建者的实现类。
Director:决定如何构建最终产品的算法。
关于例子,我就直接拿连接的代码来说了(以电脑配置为例子):
// Product产品
public class Computer {
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选
public Computer(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public void setUsbCount(int usbCount) {
this.usbCount = usbCount;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public void setDisplay(String display) {
this.display = display;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", usbCount=" + usbCount +
", keyboard='" + keyboard + '\'' +
", display='" + display + '\'' +
'}';
}
}
// 抽象的构建者
public abstract class ComputerBuilder {
public abstract void setUsbCount();
public abstract void setKeyboard();
public abstract void setDisplay();
public abstract Computer getComputer();
}
// 具体的构建者(苹果mac)
public class MacComputerBuilder extends ComputerBuilder {
private Computer computer;
public MacComputerBuilder(String cpu, String ram) {
computer = new Computer(cpu, ram);
}
@Override
public void setUsbCount() {
computer.setUsbCount(2);
}
@Override
public void setKeyboard() {
computer.setKeyboard("苹果键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("苹果显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
// 具体的构建者(联想)
public class LenovoComputerBuilder extends ComputerBuilder {
private Computer computer;
public LenovoComputerBuilder(String cpu, String ram) {
computer=new Computer(cpu,ram);
}
@Override
public void setUsbCount() {
computer.setUsbCount(4);
}
@Override
public void setKeyboard() {
computer.setKeyboard("联想键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("联想显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
// Director 决定了构建逻辑的指导者
public class ComputerDirector {
public void makeComputer(ComputerBuilder builder){
builder.setUsbCount();
builder.setDisplay();
builder.setKeyboard();
}
}
// 客户端
public static void main(String[] args) {
ComputerDirector director=new ComputerDirector();//1
ComputerBuilder builder=new MacComputerBuilder("I5处理器","三星125");//2
director.makeComputer(builder);//3
Computer macComputer=builder.getComputer();//4
System.out.println("mac computer:"+macComputer.toString());
ComputerBuilder lenovoBuilder=new LenovoComputerBuilder("I7处理器","海力士222");
director.makeComputer(lenovoBuilder);
Computer lenovoComputer=lenovoBuilder.getComputer();
System.out.println("lenovo computer:"+lenovoComputer.toString());
}
我们可以看到,各个组件正如描述所说那样子各司其职。
那为啥要使用构建者模式呢:
1、简化构建对象过程,当一个对象构建的时候,如果可选参数和必填参数掺杂,这就会导致许多的构建函数重载,而且构造函数的入参也会比较长。
2、解耦,客户端完全不需要知道或者部分构建细节不需要知道,通过不同的builder就可以做到构建不同需求的对象。
3、开放封闭原则,当需要扩展不同的builder的时候只需要实现抽象的builder然后动态地给Director传入不同的builder就可以完成扩展
学过javaScript的同学应该知道每一个对象(js中函数也是对象),都有一个__proto__属性,这个属性就是js暴露给用户用以访问其原型对象的属性。我们也可以给对象的prototype属性赋值指定原型,以在某种程度上实现JS继承,这也是JS的原型模式的实现。
原型模式其实就是,在创建对象的时候,不直接走原有的创建对象流程(特别是new关键字),用clone的方式复制原有的对象(也分深复制和浅复制,这个被复制对象也叫原型类的实例prototype),然后对clone结果进行操作(在原型基础上进行对对象属性值进行一些自定义的操作)。
浅复制
浅复制在复制基本类型的时候当然没有事,但是在复制引用类型的时候,就有问题了,我们先简单复习一下,堆栈,对象的引用,局部变量引用了一个对象可以理解为两个部分:
一个是“key”一个是“value”,其中key存放的内存是开辟在栈中,随着方法的消亡而消亡,value实际上是一个地址,而这个地址指向的是堆中的位置,同时这个被引用的对象就位于这个位置。浅复制就是简单的把地址复制给另一个变量,实际上这个对象是没有clone一份出来的,所以两处操作都对同一个对象修改了,如:
public static void main(String[] args) {
Object a = new Object();
Object b = a;
System.out.println(a);
System.out.println(b);
}
// 输出
java.lang.Object@3cb5cdba
java.lang.Object@3cb5cdba
深复制
深复制就是以被复制对象为基础,重新在堆中开辟一份内存,以a对象为模板,初始化b对象,对a和b对象的操作都是完全独立的。
public class FRAOnion extends Onion implements Cloneable{
FRAOnion(String name){
this.name = name;
}
@Override
protected void say() {
System.out.println("I am " + this.name+".");
}
@Override
protected FRAOnion clone() throws CloneNotSupportedException {
FRAOnion target = new FRAOnion(this.name);
return target;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
FRAOnion a = new FRAOnion("france onion");
FRAOnion b = a.clone();
System.out.println(a + " name:" + a.name);
System.out.println(b + " name:" + b.name);
}
// 输出
FRAOnion@3cb5cdba name:france onion
FRAOnion@56cbfb61 name:france onion
明显看到a和b是两个不同的对象(至少可以看到堆位置不同,看两处输出@后面的那一串16进制数据)
例子如下,我们假定订单信息是,门店打包电脑的货物是固定的,每个人下订单的时候,只有收件人和送货地址不一样,所以每次创建订单都可以从原来的货物对象中复制,扩展:
// 原型类
public abstract class Prototype {
public abstract Prototype copy();
}
// 货物类
public class Goods extends Prototype{
private List goodsList;
// 寄送地址
private String address;
// 收件人
private String addressee;
public Goods(){
}
public Goods(List goodsList){
this.goodsList = goodsList;
}
public void loadGoods(){
if (Objects.isNull(goodsList)){
goodsList = new ArrayList<>();
}
System.out.println("正在打包商品");
System.out.println("放入主机");
goodsList.add("主机");
System.out.println("放入显示器");
goodsList.add("显示器");
System.out.println("放入键盘");
goodsList.add("键盘");
System.out.println("放入鼠标");
goodsList.add("鼠标");
}
@Override
public Prototype copy() {
return new Goods(this.goodsList);
}
}
// 客户端
public static void main(String[] args) {
Goods mainGoods = new Goods();
// 程序启动时一次性加载相同的数据
mainGoods.loadGoods();
Goods tomGoods = (Goods) mainGoods.copy();
tomGoods.setAddress("Tom的家");
tomGoods.setAddressee("Tom");
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("把");
stringBuffer.append(tomGoods.getGoodsList().stream().collect(Collectors.joining(",")));
stringBuffer.append("送到");
stringBuffer.append(tomGoods.getAddress());
stringBuffer.append(",收件人是");
stringBuffer.append(tomGoods.getAddressee());
System.out.println(stringBuffer.toString());
}
// 输出
正在打包商品
放入主机
放入显示器
放入键盘
放入鼠标
把主机,显示器,键盘,鼠标送到Tom的家,收件人是Tom
如果用一个更加实际一点的例子来说就是,如果工厂内,当天的生产产品数据,次日必须要发送到各个部门领导邮箱中,但是生产数据的数据量比较多,而且还需要一定的计算,但是当天的生产数据及结果都是固定的,就是每个数据的收件人以及title不一样,这个时候我们就可以考虑用原型模式,单次执行查询数据库,计算结果,然后每次发送的时候,都从这个原型对象中复制一份出来(在内存中复制),发送出去。
适配器模式(Adapter Pattern)是指将一个类的接口转换成用户期待的另一个接口,使原本接口不兼容的类可以一起工作,属于构造设计模式。
一些概念
1、Target,就是客户端定义的一些接口
2、Adaptee,客户端希望使用的一些开发好的接口
3、Adapter,适配器类,此模式的核心。它需要实现客户端接口Target,而且必须要引用Adaptee,实现时使用adaptee方法,以达到adaptee与target不兼容的问题。
代码如下:
// target接口
public interface TargetLogger {
void debug(String tag,String message);
}
// adaptee接口
public interface Log4jLogger {
void debug(int weight,String message,Object ...objs);
}
// Adapter
public class L4JAdapter implements TargetLogger{
private Log4jLogger log4jLogger;
public L4JAdapter(Log4jLogger log4jLogger){
this.log4jLogger = log4jLogger;
}
@Override
public void debug(String tag, String message) {
if (Objects.nonNull(this.log4jLogger)){
log4jLogger.debug(1,message);
}
}
}
业务需求,TargetLogger.debug是本公司自定义的debug打印日志方式,其中打印细节完全由自研自己开发完成,现经过考核发现第三方接口Log4jLogger更加适用,但是Log4jLogger的debug方法与TargetLogger的debug方法完全不兼容,这个时候就需要一个L4JAdapter去适配TargetLogger和Log4jLogger ,如此优化可以把升级工作量降低到最少(仅仅把L4JAdapter替换原有的TargetLogger 实现类即可)。
注意
1、如果我们客户端同一个类中,一部分的接口是希望保留旧厂商的接口,一部分的接口希望扩张B厂商的代码,这个时候,适配器可以作为一个双向适配器使用。
装饰模式是在不必改变原类和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
重点
不必改变原类和继承不再适用,动态地扩展一个对象的功能(为其增加责任)。
理解
假如现在A公司要开发一个智能煮饭机器人,可以连接水源,存入米,设定时间后可以开始煮饭。我们可以定义如下接口并且实现。
// 抽象类
public interface CookerRobot {
void cookRice();
}
// 实现类
public class OriginalCookerRobot implements CookerRobot{
@Override
public void cookRice() {
System.out.println("已煮熟饭 ");
}
}
但是现在因为要适应市场需要,客户反馈希望这个煮饭机器人不仅仅可以做白米饭,还想做韩式拌饭,有人喜欢加泡菜,有人喜欢加五花肉,有人喜欢先加泡菜搅拌后再加入肉松和培根……,明显这个时候继承已经不太适用,除非你愿意罗列所有原材料的排列组合(原材料越多)。这个时候可以考虑使用装饰器模式。
// 翻炒
public class StirFryDecorator implements CookerRobot{
private CookerRobot cookerRobot ;
public StirFryDecorator (CookerRobot cookerRobot){
this.cookerRobot = cookerRobot;
}
@Override
public void cookRice() {
cookerRobot.cookRice();
this.stirFry();
}
private void stirFry(){
System.out.println("翻炒 ");
}
}
// 加入泡菜
public class VegetableDecorator implements CookerRobot{
private CookerRobot cookerRobot ;
public VegetableDecorator(CookerRobot cookerRobot){
this.cookerRobot=cookerRobot;
}
@Override
public void cookRice() {
cookerRobot.cookRice();
this.addVegetable();
}
private void addVegetable(){
System.out.println("加入泡菜 ");
}
}
// 加入培根
public class BaconicDecorator implements CookerRobot{
private CookerRobot cookerRobot;
public BaconicDecorator(CookerRobot cookerRobot){
this.cookerRobot = cookerRobot;
}
@Override
public void cookRice() {
cookerRobot.cookRice();
this.addBaconic();
}
private void addBaconic(){
System.out.println("加入培根 ");
}
}
// 客户端
public class Client {
public static void main(String[] args) {
CookerRobot cookerRobot = new OriginalCookerRobot();
cookerRobot = new VegetableDecorator(cookerRobot);
cookerRobot = new StirFryDecorator(cookerRobot);
cookerRobot = new BaconicDecorator(cookerRobot);
cookerRobot.cookRice();
}
}
这样子客户端就可以动态地有选择性地去预设一个合理的做饭流程,避免了利用继承和实现产生大量的子类实现类。
实际的例子,CachingExecutor掌握mybatis的应该都知道,CachingExecutor类直接实现了Excutor接口,是装饰器类,主要用来增强缓存相关功能,具体增强的代码如下:
// 只显示关键代码(CachingExecutor)
public class CachingExecutor implements Executor {
private final Executor delegate;
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
List list = (List)this.tcm.getObject(cache, key);
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
// 只显示关键代码(BaseExecutor)
public abstract class BaseExecutor implements Executor {
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
}
我们可以看到CachingExecutor(装饰器,二级缓存),相对于BaseExecutor来说,BaseExecutor是具体的实现类,主要包含的是BaseExecutor的本身职责,CachingExecutor(装饰器)会在其他的实现类上面增强一些额外的职责。(当然我们可以反思,是不是这个CachingExecutor也可以写成,复制一份BaseExecutor的代码再加上CachingExecutor(装饰器,二级缓存)就OK了呢,即通过普通的继承方式,但是后面有ReuseExecutor和SimpleExecutor,是不是意味着用继承来排列组合,很明显这里装饰器更加优雅更加容易扩展一些)。
外观模式实际上就是对一个子系统的简化。
下面一个例子:
我们可能家里有一个私人影院,每次在家播放电影的时候可能都需要调暗灯光,放下屏幕,打开投影仪,播放机接入到投影仪输出,打开播放器,打开环绕音,设置足够大的音量等等几步操作。那如果用外观模式来优化的话,我们肯定希望有一个开关,一键打开的时候就帮我们完成这些功能,一键关闭就可以完整这些功能的关闭操作。代码我就不写了,这里我们想象,每一个独立的机器就是一个类,每一个机器的功能为类的实例方法即可。
1、创建一个Facade类,on方法包含调暗灯光,放下屏幕,打开投影仪,播放机接入到投影仪输出,打开播放器,打开环绕音,设置足够大的音量操作。
2、off方法包含on方法的关闭操作即可。
注意点
1、外观类实际上不是封装,而是某个子系统功能的集合,提供一个简化了的子系统接口,同时也允许直接调子接口
2、外观类实际上不会扩展子系统,而是让子系统足够的简单合理,比如,外观类懂得先打开播放器才能设置环绕音,和调节音量。
3、每个子系统可以有多个外观包装,而且外观也有解耦的功能,客户端实际上调用的是外观类,当某个子系统需要升级或者整个替换,我们只需要重新编写一个外观即可,即子系统和客户端解耦。
4、与装饰器模式相比外观更加关注的是简化,而装饰器模式更加关注的是一个或者多个类的接口,通过适配器转化成客户端希望的形式。
理解
1、代理模式实际上就是对象访问的控制。
2、与装饰者模式不同的点就是,装饰者模式重点是给接口增加职责。
3、用工厂模式生产对象(给客户端屏蔽对象的生产方式),可以让客户端在不知觉的情况下使用了代理对象而完全不自知。
4、与适配器模式不同的点就是,适配器模式重点在于改变对象适配的接口,而代理模式是直接把参数转发给被代理对象方法。
动态代理
jdk的动态代理有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作的,而Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。
下面我们来看一个例子:
淘宝店都有一个共同的管理,平时他都不管每个店到底在买卖什么,但是今天他突然觉得有必要监控每一个店的货物流水,但是又不想被客户发现他买东西的时候,商品信息被窥探,所以管理员现在在每个客户进入淘宝店的时候,给它的都是一个淘宝店的代理对象,而客户完全不自知,并且在它每一次购物的时候记录下购买的商品。
// 店铺接口
public interface Shop {
void sell(String goods);
}
// 淘宝店实现类
public class TaoBaoShop implements Shop{
@Override
public void sell(String goods) {
System.out.println("淘宝贩卖货品:"+goods);
}
}
// 代理类
public class ShopMonitor implements InvocationHandler {
private Object shop;
public ShopMonitor(Object shop){
this.shop = shop;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (args.length>0){
System.out.println("我把你的订单截取了,我看看你买了啥:" + args[0]);
}
Object result=method.invoke(shop,args);
return result;
}
}
// 店铺生成工厂(简单写一下)
public class ShopFactory {
public static Shop getShop(Object target){
InvocationHandler handler = new ShopMonitor(target);
return (Shop) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Shop shop = ShopFactory.getShop(new TaoBaoShop());
shop.sell("大宝贝");
}
}
执行结果:
我把你的订单截取了,我看看你买了啥:大宝贝
淘宝贩卖货品:大宝贝
更加真实的例子,我们应该使用过远程调用,实际上远程调用实现的方法,就是远程代理,修改代理类代码。
// 代理类
public class ShopMonitor implements InvocationHandler {
private Object shop;
public ShopMonitor(Object shop){
this.shop = shop;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 假设我现在要改为远程调用,其他微服务的接口,以http请求为例子
// 获取远程调用的地址
// 创建http客户端,创建连接等
// 组织请求头,把args转化为httpRequest的param
// 发送请求并等待返回
// 获取返回结果,反序列化为method的返回值result
// 返回结果
}
}
通过上面的方式,截获了用户调用方法,实际上是发起一个HTTP请求到远程的微服务,把HTTP请求返回的数据重新组织为对象,返回。
另外的根据代理的实现方式不同我们可以分类如下几种代理
1、远程代理 :为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
2、虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
3、缓冲代理:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
4、保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
cgLib的动态代理实现
这里指的是,通过cgLib 库实现的代理方式,它的原理是动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
底层是使用字节码处理框架ASM(对JVM内部结构包括class文件的格式和指令集都很熟悉才能处理得了),来转换字节码并生成新的类。
把上诉淘宝店管理人的例子改为cgLib的动态代理实现:
// 工厂类
public class ShopFactory {
public static Object getShop2(Object target){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new ShopMonitor2());
Object targetProxy= enhancer.create();
return targetProxy;
}
}
// 代理类
public class ShopMonitor2 implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (objects.length>0){
System.out.println("我把你的订单截取了,我看看你买了啥:" + objects[0]);
}
Object result=methodProxy.invokeSuper(o,objects);
return result;
}
}
// 客户端调用
public static void main(String[] args) {
TaoBaoShop shop = (TaoBaoShop)ShopFactory.getShop2(new TaoBaoShop());
shop.sell("大宝贝");
}
// TaoBaoShop 不变
输出结果
我把你的订单截取了,我看看你买了啥:大宝贝
淘宝贩卖货品:大宝贝
观察者模式的核心就是发布订阅,也是一种一对多的关系,当一个对象改变状态其他依赖者都会收到通知,其结构也简单,它包含:
Subject:被观察者,主题
ConcreteSunject:被观察者实现类
Observer:观察者
ConcreteObserver:观察者实现类
下面我们通过用java代码去模拟js实现vue以数据驱动页面元素的例子来说明,如v-show能通过score内的某个值(假定我们叫他isShow,根据isShow的true或false)来进行展示和隐藏,其中一系列的DOM元素就是Observer,而isShow这个变量就是ConcreteSunject:
// Subject 主题,同时他也是一个js成员变量抽象接口
public interface Param {
void addObserver(Document document);
void removeObserver(Document document);
void notifiedAll();
boolean getValue();
void setValue(boolean value);
}
// Observer观察者 是页面的dom元素
public abstract class Document {
private String id;
public Document(String id){
this.id = id;
}
public String getId() {
return id;
}
abstract void update(boolean value);
}
// ConcreteSunject 是一个名为IsShow 的变量,当IsShow的值发生变化时应当通知所有订阅了的dom元素,让他们作出反应
public class IsShow implements Param{
private Map observer = new HashMap<>();
private boolean value = true;
// 绑定成功的时候马上通知dom元素根据现在的param值进行样式变化
@Override
public void addObserver(Document document) {
observer.put(document.getId(),document);
notifiedAll();
}
@Override
public void removeObserver(Document document) {
observer.remove(document.getId());
}
@Override
public void notifiedAll() {
observer.entrySet().stream().peek(e->e.getValue().update(value)).count();
}
@Override
public boolean getValue() {
return value;
}
// 只有当值真的被改变的时候才通知所有的订阅者
@Override
public void setValue(boolean value) {
boolean isNotified = false;
if (value != this.value){
isNotified = true;
}
this.value = value;
if (isNotified){
notifiedAll();
}
}
}
// ConcreteObserver 是一个div
public class Div extends Document{
Map style = new HashMap<>();
public Div(String id) {
super(id);
}
// 当主题值发生变化时,更新样式
@Override
void update(boolean value) {
if (value){
style.put("visibility","visible");
} else {
style.put("visibility","hidden");
}
this.display();
}
// 重新渲染
@Override
void display(){
System.out.println("I am "+this.getId() + " my style is:");
Optional.ofNullable(style).orElse(new HashMap<>()).entrySet().stream().peek(e-> System.out.println(e.getKey()+":"+e.getValue()+";")).count();
}
}
// 客户端
public static void main(String[] args) {
Document div = new Div("background");
Param isShow = new IsShow();
isShow.addObserver(div);
isShow.setValue(false);
isShow.setValue(true);
}
// 输出如下
I am background my style is:
visibility:visible;
I am background my style is:
visibility:hidden;
I am background my style is:
visibility:visible;
这就是vue以数据驱动元素,通过观察者模式的实现,我们发现,dom的visibility属性根据isShow的值进行变化,当isShow的值(value,主题)发生变化时,dom(订阅者)会受到通知,然后根据主题的内容(true or false)进行visibility:visible;或者visibility:hidden;