设计模式之备忘录实战分析

备忘录模式实战

  • 前面文章依次介绍了Java设计模式中的备忘录模式以及其在Android源码中的实现,相信很多人和我一样,知其然但不知其所以然。俗话说时间是检验真理的唯一标准。现在就跟我来进行实战分析吧!
  • 本次采用一个简单的记事本案例,通过记事本的撤销,重做,保存等逻辑,使用备忘录模式对其代码重构。先看一下人人都会写的部分吧:
    <LinearLayout

          android:layout_width="368dp"
          android:layout_height="495dp"
          android:orientation="vertical"
          tools:layout_editor_absoluteX="8dp"
          tools:layout_editor_absoluteY="8dp">
          <EditText
              android:id="@+id/edit_text"
              android:layout_width="match_parent"
              android:layout_height="0dp"
              android:layout_weight="1"
              android:gravity="left" />
          <RelativeLayout
              android:layout_margin="12dp"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginBottom="10dp">
              <TextView
                  android:id="@+id/tv_save"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_centerInParent="true"
                  android:layout_margin="12dp"
                  android:text="保存" />
              <TextView
                  android:id="@+id/tv_pre"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_margin="12dp"
                  android:layout_toLeftOf="@id/tv_save"
                  android:text="撤销" />
                  <TextView
                      android:id="@+id/tv_next"
                      android:layout_width="wrap_content"
                      android:layout_height="wrap_content"
                      android:layout_margin="12dp"
                      android:layout_toRightOf="@id/tv_save"
                      android:text="重做" />
    
              </RelativeLayout>
    
          </LinearLayout>复制代码

    xml布局代码,主要有三个逻辑(撤销、保存、重做)以及一个edittext空间进行数据的编辑。

  • Activity中主要进行控件初始化,点击事件的监听,代码如下:

      public class MainActivity extends AppCompatActivity implements View.OnClickListener {
          private TextView tvPre;//撤销
          private TextView tvSave;//保存
          private TextView tvNext;//重做
          private EditText etText;//编辑器
    
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              initViews();
              setOnClick();
          }
    
          private void initViews() {
              tvPre = (TextView) findViewById(R.id.tv_pre);
              tvSave = (TextView) findViewById(R.id.tv_save);
              tvNext = (TextView) findViewById(R.id.tv_next);
              etText = (EditText) findViewById(R.id.edit_text);
          }
    
          private void setOnClick() {
              tvPre.setOnClickListener(this);
              tvSave.setOnClickListener(this);
              tvNext.setOnClickListener(this);
          }复制代码
  • 撤销、保存、重做逻辑的具体实现(CareTaker作用):

          @Override
          public void onClick(View view) {
              int id = view.getId();
              switch (id) {
                  case R.id.tv_pre:
                      //获取前一个存档信息(撤销)
                      restoreeditText(getPreMemoto());
                      makeToast("撤销:");
                      break;
                  case R.id.tv_save:
                      //保存记录信息
                      saveMemoto(createMemotoForEditText());
                      makeToast("保存:");
                      break;
                  case R.id.tv_next:
                      //获取下一条记录(重做)
                      restoreeditText(getNextMemoto());
                      makeToast("重做:");
                      break;
              }
          }
    
          private void makeToast(String msg) {
              Toast.makeText(this, msg + etText.getText() +
                              "光标位置" + etText.getSelectionStart(),
                      Toast.LENGTH_SHORT).show();
              System.out.println(msg + etText.getText() +
                      "光标位置" + etText.getSelectionStart());
          }复制代码
  • 备忘录的数据操作部分,可以保存30次操作记录,主要包括初始化、获取前一个存放信息、获取后一个存档信息以及恢复数据等逻辑。相当于Originator作用。

          //最大存储数量
          private static final int MAX = 30;
          //存储30条记录
          List<Memoto> mMemoto = new ArrayList<Memoto>(MAX);
          int mIndex = 0;
    
          /**
           * 保存备忘录
           */
          public void saveMemoto(Memoto memoto) {
              if (mMemoto.size() > MAX) {
                  mMemoto.remove(0);
              }
              mMemoto.add(memoto);
              mIndex = mMemoto.size() - 1;
          }
          /**
           * 获取前一个存档,相当于撤销
           *
           * @return
           */
          public Memoto getPreMemoto() {
              mIndex = mIndex > 0 ? --mIndex : mIndex;
              return mMemoto.get(mIndex);
          }
          /**
           * 获取下一个存档,相当于重做
           *
           * @return
           */
          public Memoto getNextMemoto() {
              mIndex = mIndex < mMemoto.size() - 1 ? ++mIndex : mIndex;
              return mMemoto.get(mIndex);
          }
          /**
           * 为编辑器创建Memoto对象
           *
           * @return
           */
          private Memoto createMemotoForEditText() {
              Memoto memoto = new Memoto();
              memoto.cursor = etText.getSelectionStart();
              memoto.text = etText.getText().toString();
              return memoto;
          }
          /**
           * 恢复编辑器状态
           *
           * @param memoto
           */
          private void restoreeditText(Memoto memoto) {
              etText.setText(memoto.text);
              etText.setSelection(memoto.cursor);
          }复制代码
  • Memot备忘录数据,其中记录数据的字符以及游标信息

      /**
       * Created by allies on 17-9-26.
       * 存储edittext的光标和内容
       */
    
      public class Memoto {
          //字符数据
          public String text;
          //游标信息
          public int cursor;
      }复制代码

*由上面可以看出,记事本的基本数据编辑、保存,以及特有的撤销操作和重做逻辑都已经实现。相信很多人上面的代码都可以不假思索的信手拈来,但是既然我们学习了备忘录模式,就要对其代码进行重构,学以致用方能行以千里。


以上代码的缺点:

Activity内逻辑众多,既要负责View的操作逻辑,还要管理记事本的记录、修改编辑器的状态,也就是Originator与CareTaker功能耦合在一起,造成类型膨胀,后续难以维护。我们需要优化的是将Activity中的保存数据的逻辑、职责分离出去,使每个类的职责都分工明确,降低代码之间的耦合度。

  • 优化步骤:
  1. 将Activity中的CareTaker部分抽取出来,明确Originator与CreaTaker的功能。新建一个NoteCareTaker类,负责管理Memoto对象,包括撤销、保存、重做部分的逻辑(注意不对元数据进行修改操作)。

     /**
      * Created by allies on 17-9-26.
      * 备忘录CareTaker部分逻辑
      */
     public class NoteCareTaker {
         //最大存储数量
         private static final int MAX = 30;
         //存储30条记录
         List<Memoto> mMemoto = new ArrayList<Memoto>(MAX);
         int mIndex = 0;
         /**
          * 保存备忘录
          */
         public void saveMemoto(Memoto memoto) {
             if (mMemoto.size() > MAX) {
                 mMemoto.remove(0);
             }
             mMemoto.add(memoto);
             mIndex = mMemoto.size() - 1;
         }
         /**
          * 获取前一个存档,相当于撤销
          *
          * @return
          */
         public Memoto getPreMemoto() {
             mIndex = mIndex > 0 ? --mIndex : mIndex;
             return mMemoto.get(mIndex);
         }
         /**
          * 获取下一个存档,相当于重做
          *
          * @return
          */
         public Memoto getNextMemoto() {
             mIndex = mIndex < mMemoto.size() - 1 ? ++mIndex : mIndex;
             return mMemoto.get(mIndex);
         }
     }复制代码

    NoteCareTaker中会维护一个备忘录列表,使用mIndex标识编辑器当前的记录点,可以通过相应的方法获取数据的前一个、后一个记录信息以及保存记录信息。

  2. 下面就是对Originator作用的代码重构。我们先来分析一下Originator的作用是什么?在第一篇中我们明确指出Originator作用是负责创建一个备忘录,可以记录、恢复自身的内部状态。这里我们可以看出,其可以直接操作元数据信息,也就是和数据信息耦合度最高的部分,其自身需要直接访问Memoto信息,以便通过这些信息存储和恢复数据等操作。此外最重要的是可以创建备忘录,也就是createMemoto的操作在这里执行。

     public class NoteEditText extends EditText {
         public NoteEditText(Context context) {
             this(context,null);
         }
         public NoteEditText(Context context, AttributeSet attrs) {
             this(context, attrs,0);
         }
         public NoteEditText(Context context, AttributeSet attrs, int defStyleAttr) {
             super(context, attrs, defStyleAttr);
         }
         /**
          * 创建备忘录对象,存储元数据信息
          * @return
          */
         public Memoto createMemoto(){
             Memoto memoto = new Memoto();
             memoto.text = getText().toString();
             memoto.cursor = getSelectionStart();
             return  memoto;
         }
         /**
          * 从备忘录恢复数据
          * @param memoto
          */
         public void restoreMemoto(Memoto memoto){
             setText(memoto.text);
             setSelection(memoto.cursor);
         }
     }复制代码

    从上面分析我们知道和数据接触最深的就是EditText了,我们这里直接覆写一个NoteEditTExt继承EditText,在其内部组织负责备忘录的创建以及恢复操作。这里也就突出了Originator的创建以及恢复状态的作用。

  3. 之后便是Activity中的具体操作逻辑,其直接通过自定义的NoteEditText空间创建Memoto备忘录,然后其撤销、保存、重做等逻辑不在Activity内部实现,而是转交给了CareTaker来实现。代码如下:

     public class MainActivity extends AppCompatActivity implements View.OnClickListener {
         private TextView tvPre;//撤销
         private TextView tvSave;//保存
         private TextView tvNext;//重做
         private NoteEditText etText;//自定义的编辑器
         //新建Caretaker对象,负责备忘录的撤销,恢复以及保存等操作
         NoteCareTaker mCareTaker = new NoteCareTaker();
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_main);
             initViews();
             setOnClick();
         }
         private void initViews() {
             tvPre = (TextView) findViewById(R.id.tv_pre);
             tvSave = (TextView) findViewById(R.id.tv_save);
             tvNext = (TextView) findViewById(R.id.tv_next);
             etText = (NoteEditText) findViewById(R.id.edit_text);
         }
         private void setOnClick() {
             tvPre.setOnClickListener(this);
             tvSave.setOnClickListener(this);
             tvNext.setOnClickListener(this);
         }
         @Override
         public void onClick(View view) {
             int id = view.getId();
             switch (id) {
                 case R.id.tv_pre:
                     etText.restoreMemoto(mCareTaker.getPreMemoto());
                     makeToast("撤销:");
                     break;
                 case R.id.tv_save:
                     //1.首先创建备忘录对象,创建时候就已经记录了元数据信息
                     Memoto mMemoto = etText.createMemoto();
                     //通过caretaker,保存备忘录信息
                     mCareTaker.saveMemoto(mMemoto);
                     makeToast("保存:");
                     break;
                 case R.id.tv_next:
                     //重做
                     etText.restoreMemoto(mCareTaker.getNextMemoto());
                     makeToast("重做:");
                     break;
             }
         }
         private void makeToast(String msg) {
             Toast.makeText(this, msg + etText.getText() +
                             "光标位置" + etText.getSelectionStart(),
                     Toast.LENGTH_SHORT).show();
             System.out.println(msg + etText.getText() +
                     "光标位置" + etText.getSelectionStart());
         } 
     }复制代码

    从上面可以看出:Activity中的逻辑简单化了不止一点两点,各个类的职责更加单一、明确。界面相关的类型和业务处理逻辑的类型隔离开来,避免类型膨胀,逻辑混乱等问题。在优化代码前一定要着重思考各个类的主要职责,明确他们之间的关系和功能,使得各个类各司其职,不可越过雷池半步。往往在这个时候,就要多看看这个设计模式中,各个角色所担任的职责,找准切入点在进一步优化重构代码。

总结
  • 优点
  1. 给用户提供一种可以恢复状态的机制,能够使用户方便的恢复到某个历史状态
  2. 实现数据信息的封装,使得用户不用关心其状态的保存细节
  • 缺点
    消耗资源,如果类的成员变多,势必会占用较大的资源,而且每一次保存都会消耗一定的内存(降低耦合导致类增多,各个类分工明确单一的后果)

参考:Android源码设计模式实战与分析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值