「设计模式(五) - 代理模式」

「设计模式(五) - 代理模式」

一、处处可见的“代理”

“代理”在平常生活司空见惯,点外卖,租房子找中介,买飞机票等等。基本上用手机就能完成,也就是不直接接触到对象而通过中介的方式达成自己的需求,它和装饰器在实现上是有点相似的,容易混淆。就目前的项目当中其实没用到代理模式,但还是有必要了解这种结构型模式

二、代理模式 Proxy

通俗的讲,出于某种原因不能够直接访问目标对象,如目标对象是受保护的内容、远程内容、权限问题等等。需借助代理对象作为调用者与目标对象的中介来操作目标对象的一种结构型设计模式Proxy,但是真正完成操作的还是目标对象本身。

三、结构组成与UML
  • 先看UML结构类图


    代理模式.png
  • Subject 抽象的主题类:以接口或者抽象类的形式所定义的代理类与目标类所包含的属性或者操作。
  • RealSubject 具体的目标实现类:实现(继承)Subject,实现了具体的功能细节或操作。
  • Proxy 代理对象:与RealSubject共同实现(继承)Subject,以便于Client端想操作目标对象RealSubject时转而使用Proxy代为操作,持有了目标对象RealSubject的引用。
四、实现一个简单的例子

设计一个简单的水果的价格更新系统,主要包含水果类Product,更新水果价格的接口ProductApi,更新水果价格目标实现类ProductApiImp,更新水果价格的代理类ProductApiProxy

  • 定义普通的水果类Product
/**
 * Created by Sai
 * on: 15/01/2022 18:07.
 * Description:
 */
public class Product {
    private long price;
    private String id;
    private String name;

    public Product(long price, String id, String name) {
        this.price = price;
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Product{" +
                "price=" + price +
                ", id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 更新水果价格ProductApi(Subject)
/**
 * Created by Sai
 * on: 15/01/2022 17:44.
 * Description:
 */
public interface ProductApi {
    void updatePrice(String id, long price);
}
  • 更新水果价格的具体实现类ProductApiImp(RealSubject)
/**
 * Created by Sai
 * on: 15/01/2022 17:50.
 * Description:
 */
public class ProductApiImp implements ProductApi {

    private Map<String, Product> productMap;

    public ProductApiImp() {
        productMap = new ConcurrentHashMap<>();
        addProduct();
    }

    @Override
    public void updatePrice(String id, long price) {
        if (null == productMap || productMap.isEmpty()) {
            return;
        }
        Product product = productMap.get(id);
        if (null == product) {
            System.out.println("Do not found the product, where the id is " + id);
            System.out.println("----------------------------------------------->");
            return;
        }
        System.out.println("Before update the info is " + product);
        System.out.println("----------------------------------------------->");
        product.setPrice(price);
        System.out.println("UPDATE products set price is " + price);
        System.out.println(product);
        productMap.put(id, product);
    }

    private void addProduct() {
        if (null == productMap) return;
        Product apple = new Product(500, "1", "Apple");
        Product banana = new Product(1500, "2", "Banana");
        Product orange = new Product(500, "3", "Orange");
        productMap.put(apple.getId(), apple);
        productMap.put(banana.getId(), banana);
        productMap.put(orange.getId(), orange);
    }

    public void clear() {
        if (null != productMap) {
            productMap.clear();
            productMap = null;
        }
    }
}

ProductApiImp中持有了一些具体商品的集合Product,客户端通过操作代理对象ProductApiProxy完成对指定商品价格的更新。

  • 更新水果价格的代理类ProductApiProxy(Proxy)
/**
 * Created by Sai
 * on: 15/01/2022 17:52.
 * Description:
 */
public class ProductApiProxy implements ProductApi {
    private final ProductApiImp productApiImp;

    public ProductApiProxy() {
        productApiImp = new ProductApiImp();
    }

    @Override
    public void updatePrice(String id, long price) {

        productApiImp.updatePrice(id, price);
    }

    public void destroy() {
        productApiImp.destroy();
    }
}

ProductApiProxy持有了目标对象ProductApiImp,当客户端想要更新水果信息时只需要操作代理对象即可完成目标对象对信息的更新操作。

  • 测试客户端类
/**
 * Created by Sai
 * on: 15/01/2022 18:09.
 * Description:
 */
public class Demo {

    public static void main(String[] args) {
        ProductApi productApi = new ProductApiProxy();
        productApi.updatePrice("1", 400);
    }
}
  • 打印信息
Before update the info is Product{price=500, id='1', name='Apple'}
----------------------------------------------->
UPDATE products set price is 400
Product{price=400, id='1', name='Apple'}

Process finished with exit code 0
  • 以上作为UML类图的标准实现方式,但其实实际开发过程中很难做到与这种完全符合的情况,那么在理解掌握之后应该灵活变通,还是以上述为例采用继承的方式。

Product类保持不变

/**
 * Created by Sai
 * on: 15/01/2022 18:07.
 * Description:
 */
public class Product {
    private long price;
    private String id;
    private String name;
  
    public Product(String id) {
        this.id = id;
    }

    public Product(long price, String id, String name) {
        this.price = price;
        this.id = id;
        this.name = name;
    }

    public long getPrice() {
        return price;
    }

    public void setPrice(long price) {
        this.price = price;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Product{" +
                "price=" + price +
                ", id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

修改ProductApiImpV2实现,取消原有的实现接口方式

public class ProductApiImpV2 {

    private Map<String, Product> productMap;

    public ProductApiImpV2() {
        productMap = new ConcurrentHashMap<>();
        addProduct();
    }

    public Product getProduct(String id) {
        return new ProductProxy(id, this);
    }

    private void addProduct() {
        if (null == productMap) return;
        Product apple = new Product(500, "1", "Apple");
        Product banana = new Product(1500, "2", "Banana");
        Product orange = new Product(500, "3", "Orange");
        productMap.put(apple.getId(), apple);
        productMap.put(banana.getId(), banana);
        productMap.put(orange.getId(), orange);
    }

    public void destroy() {
        if (null != productMap) {
            productMap.clear();
            productMap = null;
        }
    }

    public void updatePrice(String id, long price) {
        if (null == productMap || productMap.isEmpty()) {
            return;
        }
        Product product = productMap.get(id);
        if (null == product) {
            System.out.println("Do not found the product, where the id is " + id);
            System.out.println("----------------------------------------------->");
            return;
        }
        System.out.println("Before update the info is " + product);
        System.out.println("----------------------------------------------->");
        product.setPrice(price);
        System.out.println("UPDATE products set price is " + price);
        System.out.println(product);
        productMap.put(id, product);
    }

    public void saveChanges(String id, long price) {
        if (null == productMap || productMap.isEmpty()) {
            return;
        }
        Product product = productMap.get(id);
        if (null == product) {
            System.out.println("Do not found the product, where the id is " + id);
            System.out.println("----------------------------------------------->");
            return;
        }
        System.out.println("Before change the info is " + product);
        System.out.println("----------------------------------------------->");
        product.setPrice(price);
        productMap.put(id, product);
    }
}

修改代理类为ProductProxy以实现的方式取代继承

public class ProductProxy extends Product {
    private final ProductApiImpV2 apiImpV2;

    public ProductProxy(String id, ProductApiImpV2 apiImpV2) {
        super(id);
        this.apiImpV2 = apiImpV2;
    }

    @Override
    public void setPrice(long price) {
        super.setPrice(price);
        if (null != apiImpV2) {
            apiImpV2.saveChanges(getId(), price);
            apiImpV2.updatePrice(getId(), price);
        }
    }
}

测试类Demo

public class Demo {

    public static void main(String[] args) {
//        ProductApi productApi = new ProductApiProxy();
//        productApi.updatePrice("1", 400);

        ProductApiImpV2 apiImpV2 = new ProductApiImpV2();
        Product product = apiImpV2.getProduct("2");
        product.setPrice(450);
    }
}
//打印信息
Before change the info is Product{price=1500, id='2', name='Banana'}
----------------------------------------------->
Before update the info is Product{price=450, id='2', name='Banana'}
----------------------------------------------->
UPDATE products set price is 450
Product{price=450, id='2', name='Banana'}

Process finished with exit code 0
1.有什么弊端?
  • 如果在目前的构建的系统当中需要增加需求-新增删除操作,那么需要修改的地方包括SubjectRealSubjectProxy,显然易见的,对于扩展性代理模式支持的并不是很友好。
  • 在调用者与真实对象中间加了一层中介,系统的效率会打折扣。同时复杂度与中介类增加了。
2.有什么优点?
  • 客户端与真实对象之间存在一层代理,真实对象可以受到“保护”。
  • 中间代理层可以做更多的附加操作,例如对权限的校验增加其他业务逻辑功能等。
五、动态代理
  • 区别于静态代理,静态代理的实现方式决定了它的灵活性差,真实实现类与代理类都需要实现相同的接口。而为了解决这种困境,动态代理能够很好的满足。
  • JDK中给出了动态代理的实现方法-newProxyInstance。并且动态代理采用的是运行时动态生成,其次代理的对象不在需要实现Subject接口,仅仅只需要目标对象实现Subject即可,看看具体如何实现。
1.还是以更新水果价格为例
  • 定义更新水果价格的接口IProduct,包含一个更新的方法:
/**
 * Created by Sai
 * on: 16/01/2022 23:17.
 * Description:
 */
public interface IProduct {
    void update(String id, long price);
}
  • 动态代理的代理对象不再需要实现接口,而目标对象一定要实现接口,以IProductImpl为目标对像则:
/**
 * Created by Sai
 * on: 16/01/2022 23:26.
 * Description:
 */
public class IProductImpl implements IProduct {

    private final Map<String, Product> productMap;

    public IProductImpl() {
        productMap = new ConcurrentHashMap<>();
        addProduct();
    }

    @Override
    public void update(String id, long price) {
        markChange(id, price);
    }

    private void addProduct() {
        if (null == productMap) {
            return;
        }
        Product apple = new Product(500, "1", "Apple");
        Product banana = new Product(1500, "2", "Banana");
        Product orange = new Product(500, "3", "Orange");
        productMap.put(apple.getId(), apple);
        productMap.put(banana.getId(), banana);
        productMap.put(orange.getId(), orange);
    }

    private void markChange(String id, long price) {
        if (null == productMap || productMap.isEmpty()) {
            return;
        }
        Product product = productMap.get(id);
        if (null == product) {
            System.out.println("Do not found the product, where the id is " + id);
            System.out.println("----------------------------------------------->");
            return;
        }
        System.out.println("Before update the info is " + product);
        System.out.println("----------------------------------------------->");
        product.setPrice(price);
        System.out.println("UPDATE products set price is " + price);
        System.out.println(product);
        productMap.put(id, product);
    }
}
  • 代理对象为ProductManager,此时不再需要实现Iproduct接口,取而代之是需要实现Java反射中InvocationHandler接口:
/**
 * Created by Sai
 * on: 16/01/2022 23:18.
 * Description:
 */
public class ProductManager implements InvocationHandler {
    private IProduct iProduct;

    public IProduct getInstance(IProduct iProduct) {
        this.iProduct = iProduct;
        Class<?> clazz = iProduct.getClass();
        return (IProduct) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.iProduct, args);
    }
}
  • 测试类Demo
/**
 * Created by Sai
 * on: 16/01/2022 23:39.
 * Description:
 */
public class Demo {
    public static void main(String[] args) {

        ProductManager manager = new ProductManager();
        IProduct instance =  manager.getInstance(new IProductImpl());
        instance.update("3", 700);
    }
}

//打印的信息
Before update the info is Product{price=500, id='3', name='Orange'}
----------------------------------------------->
UPDATE products set price is 700
Product{price=700, id='3', name='Orange'}

Process finished with exit code 0
  • 当然,动态代理内部实现还是值得仔细研究一下的;而这仅仅是应用层面的简单实用,复杂的逻辑JDK已经帮我们做好了,了解原理同样很有必要。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,303评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,478评论 3 415
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,230评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,936评论 1 309
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,688评论 6 409
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,174评论 1 323
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,243评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,402评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,932评论 1 334
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,771评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,971评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,514评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,209评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,631评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,863评论 1 283
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,640评论 3 391
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,949评论 2 373

推荐阅读更多精彩内容