美文网首页Java设计模式
简易理解设计模式之:代理模式——iOS视图控件设计方式

简易理解设计模式之:代理模式——iOS视图控件设计方式

作者: 大亮亮亮亮 | 来源:发表于2019-01-13 17:10 被阅读8次

    介绍:

    代理模式属于结构型模式,也叫委托模式。作用:为其他对象提供一种代理以控制这个对象的访问。

    类图:

    代理模式UML类图.png

    Subject(抽象主题类):声明真实主题与代理的共同接口方法
    RealSubject(真实主题类):负责执行具体的任务,客户端可以通过代理类间接的调用真实主题类的方法
    Proxy(代理类):持有对真实主题类的引用,负责调用真实主题类中相应的接口方法
    Client(客户端类):使用代理对象

    用法:

    当无法或不想直接访问某个对象或访问某个对象存在的困难时可以通过一个代理对象来间接,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。

    使用场合如下:
    1、远程代理:为一个对象在不同的地址空间提供局部代表。这样可以隐藏了一个对象存在于不同地址空间的事实。
    2、虚拟代理:根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。
    3、安全代理:用来控制真实对象访问时的权限。
    4、智能指引:指当调用真实的对象时,代理处理另外一些事情。

    个人理解:
    以上一大段东西可以归纳总结为间接,需要间接时使用。代理模式可以处理的事情非常多,下面就用例子来说明。

    例子:

    代理模式的例子好容易理解,也是比较常用的设计模式。我们网上购物的过程,而是通过App(代理)进行购买,而不是直接去店家买。投资股票的行为也是通过证券公司(代理)购买上市公司的股票,而不是直接去该上市公司进行投资。

    1、简单使用代理模式:

    首先以股票投资为例,简单过一遍这个模式。

    需求:模拟股票买卖过程

    1.1、原始代码

    有一个投资者,有一个上市公司,就可以进行股票交易了。

    投资例子1.png

    没有代理模式的例子就是这样,因为股票投资不能直接跑去上市公司的地方找老板买卖股票。虽然我持有上市公司的股票但不等于上市公司与我是相互认识的。需要通过券商投资。

    1.2、使用代理模式

    投资例子2.png

    使用代理模式后类图如上所述:交易接口是一个Subject类,声明了买卖行为;
    投资者是一个RealSubject类,买股票的需求来自于他;证券公司是一个Proxy类,代理投资者进行股票交易。下面一步一步看:

    1.2.1 声明一个交易接口

    public interface Trade {
        //买股票
        void buy(Company company);
    
        //卖股票
        void sell(Company company);
    }
    
    

    1.2.2、新建一个RealSuject投资者类,实现接口执行具体的方法:

    public class Investor implements Trade {
    
        private String name;
    
        public Investor(String name) {
            this.name = name;
        }
    
        //买股票
        public void buy(Company company) {
            System.out.println(name + "【买入股票】:" + company.name);
        }
    
        //卖股票
        public void sell(Company company) {
            System.out.println(name + "【卖出股票】:" + company.name);
        }
    }
    

    1.2.3、新建一个Proxy证券公司类,持有一个真实主题对象的引用

    public class Securities implements Trade {
        private Trade trade;
    
        public Securities(Trade trade) {
            this.trade = trade;
        }
    
        @Override
        public void buy(Company company) {
            trade.buy(company);
            System.out.println("证券公司操作买入");
        }
    
        @Override
        public void sell(Company company) {
            trade.sell(company);
            System.out.println("证券公司操作卖出");
        }
    }
    

    1.2.3、调用代码

    public class Client {
        public static void main(String[] args) {
            
            Company company = new Company("大米科技");
    
            //真实主题类RealSubject
            Investor investor = new Investor("股神");
    
            //代理类,持有一个真实主题类的引用
            Securities securities = new Securities(investor);
            //调用真实主题类中相应的接口方法
            securities.buy(company);
            securities.sell(company);
        }
    }
    

    1.2.4、输出结果

    股神【买入股票】:大米科技
    证券公司操作买入
    股神【卖出股票】:大米科技
    证券公司操作卖出
    

    以上就是用Java实现的静态代理模式了,而动态代理模式则利用反射的原理去实现,此处不继续展开了。

    以下章节将会有一点点复杂,希望大家继续耐心阅读~

    2、代理模式的实际应用:

    前人种树后人乘凉,还记得我们应用场景总结有4点吧?但我们实际开发过程中并不是一定会遇到跟上述4点相似的业务才能使用代理模式,更多是为了代码的重构和优化而使用。这里暂不讨论远程代理的情况,我们讨论其余三种比较常用的业务。

    2.1、虚拟代理和延迟加载
    对客户端而言,它不能分辨出代理对象与真实对象的区别,它也无须分辨代理对象和真实对象的区别,所以叫虚拟代理。虚拟代理的用法一般用在延迟加载的业务上,下面用加载大图做例子:

    需求:加载一个大图片

    2.1.1、原始代码

    public class BigPicture {
    
        public BigPicture() {
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void show() {
            System.out.println("输出大图!");
        }
    }
    

    现在一个类的功能是模拟加载大图资源,最后展示出来。以上方法的确能实现需求,但当系统初始化这个对象时,开销非常大会造成卡顿现象。那用代理模式又该怎么优化呢?

    2.1.2、使用代理模式优化
    首先我们按照代理模式的类图,抽离一下:

    public abstract class Picture {
        public abstract void show();
    }
    

    修改BigPicture类,让它继承Picture类:

    public class BigPicture extends Picture {
    
        public BigPicture() {
            System.out.println("初始化真实对象");
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void show() {
            System.out.println("输出大图!");
        }
    }
    

    再看看非常重点的代理类:

    public class PictureProxy extends Picture {
        private Picture picture;
    
        public PictureProxy(){
            System.out.println("初始化代理对象");
        }
    
        @Override
        public void show() {
            if (picture == null) {
                picture = new BigPicture();
            }
            picture.show();
        }
    }
    

    最后调用:

    public class Test {
        public static void main(String[] args) {
            PictureProxy pictureProxy = new PictureProxy();
            pictureProxy.show();
        }
    }
    
    初始化代理对象
    初始化真实对象 //使用时输出
    输出大图!
    

    这里有必要说明一下,可能你会思考:这代码看不出有什么优化的效果,该耗时的还是在耗时?的确是,耗时操作是避免不了的。而这种做法的巧妙之处在于初始化对象的时候,也就是new方法。

    系统启动时,我们避免不了大量new很多对象,如果在new的过程中存在消耗很多资源的情况,使用代理模式分离,使用一个代理对象替代它的原有的位置,可以加快系统的启动速度。而在用户真正做具体事情时再由代理类单独去加载真实类,完成用户的请求。这个过程就是使用代理模式实现了延迟加载。

    2.2、安全代理和智能指引
    在系统开发中,我们有时需要保护某些对象的访问权限,实现端只需要知道自己需要用的方法即可,其它额外的事情均有代理端控制。

    举一个移动端开发的例子。iOS开发中存在大量的原生自带的视图控件,在视图控制器中使用控件并不需要复杂的生成一堆对象和设置各种属性,只需要简简单单调用几个实现方法即可。苹果iOS视图控件的设计中可以说将这个模式提现得淋漓尽致。下面我用Java的方法模拟一下:

    ios例子.png

    抽象主题类:接口方法(视图只用这些方法就够了)
    真实主题类:视图控制器(具体实现的方法,比如设置各种视图需要的参数等,设置完扔给代理类处理)
    代理类:视图控件(实现了很多额外的功能,你也并不需要知道我是怎么实现的)

    还是原来的配方,还是原来的味道。简单介绍一个UITableView是一个表视图控件,设置组数和行数,在iOS中比较常用。

    需求:根据组数和行数模拟使用一个UITableView视图控件

    2.2.1 Java中的实现
    定义接口,UITableViewDelegate

    public interface UITableViewDelegate {
        public int numberOfSectionsInTableView(UITableView tableView);
        public int numberOfRowsInSection(int section);
    }
    

    UITableView类(代理类),持有一个真实对象的引用

    public class UITableView implements UITableViewDelegate {
    
        private UITableViewDelegate delegate;
    
        public void initView() {
            //模拟初始化组数
            int numberOfSections = numberOfSectionsInTableView(this);
            for (int i = 0; i < numberOfSections; i++) {
                int numberOfRows = numberOfRowsInSection(i);
                for (int j = 0; j < numberOfRows; j++) {
                    System.out.print("【第" + i + "组】");
                    System.out.println("【第" + j + "行】");
                }
            }
        }
    
        public void setDelegate(UITableViewDelegate delegate) {
            this.delegate = delegate;
        }
    
        @Override
        public int numberOfSectionsInTableView(UITableView tableView) {
            return delegate.numberOfSectionsInTableView(tableView);
        }
    
        @Override
        public int numberOfRowsInSection(int section) {
            return delegate.numberOfRowsInSection(section);
        }
    
    }
    

    UIViewController,视图控制器真实主题类

    public class UIViewController implements UITableViewDelegate {
        @Override
        public int numberOfSectionsInTableView(UITableView tableView) {
            return 3; //一共3组
        }
    
        @Override
        public int numberOfRowsInSection(int section) {
            if (section == 0) {
                return 2;    //第0组有2行
            } else if (section == 1){
                return 1;   //第1组有1行
            } else {
                return 3;  //其它情况有3行
            }
        }
    }
    
    

    测试方法与输出

    public static void main(String[] args) {
        UIViewController viewController = new UIViewController();
        UITableView tableView = new UITableView();
        tableView.setDelegate(viewController);
        tableView.initView();
    }
    
    【第0组】【第0行】
    【第0组】【第1行】
    【第1组】【第0行】
    【第2组】【第0行】
    【第2组】【第1行】
    【第2组】【第2行】
    

    2.2.2、iOS中的源码
    下面从iOS的源码中找出与上述例子相似的方法出来,有兴趣的同学可以自行研究更多iOS相关的文章。下面我们过一遍源码:

    UITableViewDataSource接口,声明获得行数和组数的方法。

    @protocol UITableViewDataSource<NSObject>
    
    @required
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;  
    
    @optional
    

    UITableView.h文件,声明了表视图控件需要用到的方法,具体实现在UITableView.m文件中,很抱歉是不能看的。这部分属于代理类,但我们可以猜测它的内部实现(上述例子)。

    NS_CLASS_AVAILABLE_IOS(2_0) @interface UITableView : UIScrollView <NSCoding, UIDataSourceTranslating>
    
    @property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource;
    @property (nonatomic, readonly) NSInteger numberOfSections;
    - (NSInteger)numberOfRowsInSection:(NSInteger)section;
    
    //省略好多代码。。
    

    UIViewController就是我们用的控制器,属于真实主题类,也就是各位使用视图控件的地方。

    UIViewController.h文件

    @interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
    
    //界面中的UITableView控件
    @property (weak, nonatomic) IBOutlet UITableView *tableView;
    

    UIViewController.m文件

    //省略好多代码。。
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.tableView.dataSource = self;
    }
    
    //实现UITableViewDataSource接口部分
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
        return 1;
    }
    
    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return 2;
    }
    

    总结一下这部分,用过iOS的同学都知道,很多视图控件都是采用代理模式来设计。如果不这么做,大量UI的方法都只能写在控制器里面,造成代码臃肿。通过代理对象的方式给控制器瘦身,视图控件也得到了复用。对于控制器来说,使用一个控件视图并不需要知道它具体的内部实现,只知道自己需要实现的方法即可。

    感谢您的阅读~

    转载请注明出处喔:https://www.jianshu.com/p/e8dad2365d99

    相关文章

      网友评论

        本文标题:简易理解设计模式之:代理模式——iOS视图控件设计方式

        本文链接:https://www.haomeiwen.com/subject/dcrgrqtx.html