必做作业三:日历控件中观察者模式解析

一、观察者模式的定义

  观察者模式也被称为发布-订阅(Publish/Subscribe)模式,它属于行为型模式的一种。观察者模式定义了一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听。当这个主题对象状态变化时,会通知所有观察者对象并作出相应处理逻辑。

  观察者模式(Observer)完美的将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
 
  观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
 
二、观察者模式在项目中的应用
  
  观察者模式在许多场景都会使用到。本文选取的是github上的一个日历控件项目SuperCalendar。分析在该项目中观察者模式是如何体现的,有哪些优点。
 
(1)项目简介
  SuperCalendar是一款应用于安卓软件上的日历控件。通常使用在有课表、日历安排等需求的项目中。SuperCalendar的主要特性有以下几点:
  • 日历样式完全自定义,拓展性强
  • 左右滑动切换上下周月,上下滑动切换周月模式
  • 抽屉式周月切换效果
  • 标记指定日期(marker)
  • 跳转到指定日期

  项目的思路如下图:

 

  

   Calendar的绘制由CalendarRenderer完成,IDayRenderer实现自定义的日期效果,CalendarAttr中存储日历的属性。
 
(2)代码中观察者模式的体现
 
  下面分析日历控件中的一个点击事件:
  
public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                posX = event.getX();
                posY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                float disX = event.getX() - posX;
                float disY = event.getY() - posY;
                if (Math.abs(disX) < touchSlop && Math.abs(disY) < touchSlop) {
                    int col = (int) (posX / cellWidth);
                    int row = (int) (posY / cellHeight);
                    onAdapterSelectListener.cancelSelectState();
            renderer.onClickDate(col, row); onAdapterSelectListener.updateSelectState(); invalidate(); }
break; } return true; }

  在该段代码中,日期的状态State是被观察者。State主要通过cancelSelectState()和updateSelectState()来改变自身状态。本段代码为一个点击事件,目的是确定用户点击位置的日期。在观察者模式中,观察者观察到用户点击日期,观察者自身状态开始更新。

  在点击事件onTouchEvent触发时,日期的状态State被改变,观察者们观察到State变化,自身状态开始更新。

  观察者们的更新主要体现在以下代码中:

public void onClickDate(int col, int row) {
        if (col >= Const.TOTAL_COL || row >= Const.TOTAL_ROW)
            return;
        if (weeks[row] != null) {
            if(attr.getCalendarType() == CalendarAttr.CalendayType.MONTH) {
                if(weeks[row].days[col].getState() == State.CURRENT_MONTH){
                    weeks[row].days[col].setState(State.SELECT);
                    selectedDate = weeks[row].days[col].getDate();
                    CalendarViewAdapter.saveDate(selectedDate);
                    onSelectDateListener.onSelectDate(selectedDate);
                    seedDate = selectedDate;
                } else if (weeks[row].days[col].getState() == State.PAST_MONTH){
                    selectedDate = weeks[row].days[col].getDate();
                    CalendarViewAdapter.saveDate(selectedDate);
                    onSelectDateListener.onSelectOtherMonth(-1);
                    onSelectDateListener.onSelectDate(selectedDate);
                } else if (weeks[row].days[col].getState() == State.NEXT_MONTH){
                    selectedDate = weeks[row].days[col].getDate();
                    CalendarViewAdapter.saveDate(selectedDate);
                    onSelectDateListener.onSelectOtherMonth(1);
                    onSelectDateListener.onSelectDate(selectedDate);
                }
            } else {
                weeks[row].days[col].setState(State.SELECT);
                selectedDate = weeks[row].days[col].getDate();
                CalendarViewAdapter.saveDate(selectedDate);
                onSelectDateListener.onSelectDate(selectedDate);
                seedDate = selectedDate;
            }
        }
    }

  当State已经改变。onClickDate()方法为观察者。方法中使用weeks[row].day[col].getState()获取改变后的状态。根据改变后的状态进行更新存储在CalendarViewAdapter中,同时监听selectedDate。

  onClickDate()方法只是改变了存储中的数据。而重绘Calendar的则是另外一个观察者。

  

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        renderer.draw(canvas);
    }

public void draw(Canvas canvas) {
        for (int row = 0; row < Const.TOTAL_ROW; row++) {
            if (weeks[row] != null) {
                for (int col = 0; col < Const.TOTAL_COL; col ++) {
                    if (weeks[row].days[col] != null) {
                        dayRenderer.drawDay(canvas , weeks[row].days[col]);
                    }
                }
            }
        }
    }

public void drawDay(Canvas canvas , Day day) {
        this.day = day;
        refreshContent();
        int saveId = canvas.save();
        canvas.translate(day.getPosCol() * getMeasuredWidth(),
                day.getPosRow() * getMeasuredHeight());
        draw(canvas);
        canvas.restoreToCount(saveId);
    }

public void refreshContent() {
       renderToday(day.getDate());
       renderSelect(day.getState());
       renderMarker(day.getDate(), day.getState());
       super.refreshContent();
   }

 

  代码中重绘State改变后的日历,其中观察者为refreshContent()。onDraw调用了draw方法完成对日历的重绘。当State改变,选中的日期也改变了,此时draw方法中使用dayRenderer.drawDay(canvas , weeks[row].days[col]),dayRenderer是一个接口,在lib中有一个DayView的抽象类实现该接口。 其中的drawDay方法完成了对该天到calendar的canvas上的绘制。

 

(3)项目中使用观察者模式的好处

  以上介绍了一个被观察者State以及其对应的两个观察者onClickDate()和refreshContent()。描述了他们的功能以及代码实现。

  其中我们可以明显的感觉到了观察者与被观察者分开了。State改变时并不是直接调用类来实现改变。这样会使得观察对象与被观察对象之间紧密的耦合起来,从根本上违反面向对象的设计的原则。

  被观察者们通过观察State数值的变化,自动地根据变化后的值来更新、重绘日历。代码被清晰地分为三个模块。被观察者与观察者之间划定了清晰的界限,这样无疑也提高了应用程序的可维护性和重用性。

 

三、总结

  观察者模式其实是一种被许多人无意识中使用的软件设计模式。本文主要讨论了日历控件项目中,当用户改变日期时,日历界面以及软件内部数据所产生的自动更新。在本项目中,日期变化的代码与日历界面以及软件数据处理有明显的模块化处理,避免了依赖关系之间的耦合,符合观察者模式的主要特征。

 

项目中代码github地址:

onTouchEvent()

https://github.com/MagicMashRoom/SuperCalendar/blob/a897fb20f670195c9b938d2104cc44d4c9b70904/calendar/src/main/java/com/ldf/calendar/view/Calendar.java#L78

onClickDate()

https://github.com/MagicMashRoom/SuperCalendar/blob/a897fb20f670195c9b938d2104cc44d4c9b70904/calendar/src/main/java/com/ldf/calendar/component/CalendarRenderer.java#L59

draw()

https://github.com/MagicMashRoom/SuperCalendar/blob/a897fb20f670195c9b938d2104cc44d4c9b70904/calendar/src/main/java/com/ldf/calendar/component/CalendarRenderer.java#L42

onDraw()

https://github.com/MagicMashRoom/SuperCalendar/blob/a897fb20f670195c9b938d2104cc44d4c9b70904/calendar/src/main/java/com/ldf/calendar/view/Calendar.java#L56

drawDay()

https://github.com/MagicMashRoom/SuperCalendar/blob/a897fb20f670195c9b938d2104cc44d4c9b70904/calendar/src/main/java/com/ldf/calendar/view/DayView.java#L59

  

  

转载于:https://www.cnblogs.com/yyh1996/p/9847025.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值