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

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

一、处处可见的“代理”

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

二、代理模式 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已经帮我们做好了,了解原理同样很有必要。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容