Friday, January 29, 2021

Android Component Pattern

 

创建抽象组件角色
这里就是一个网站的抽象页面元素:

============================================================

    public abstract class PageElement {//页面
        protected List<PageElement> mPageElements = new ArrayList<>();//用来保存页面元素
        private String name;

        public PageElement(String name) {
            this.name = name;
        }

        public abstract void addPageElement(PageElement pageElement);//添加栏目或者具体内容

        public abstract void rmPageElement(PageElement pageElement);//删除栏目或者具体内容
        
        public abstract void clear();//清空所有元素

        public abstract void print(String placeholder);//打印页面结构
        
        public String getName() {
            return name;
        }
    }

============================================================

创建叶子节点
叶子节点继承了抽象组件角色,但是由于没有分支,所以一些添加删除操作是实现不了的。叶子节点都是一些具体的内容,比如具体的音乐内容、视屏内容等等。

============================================================
    public class Content extends PageElement {//具体内容

        public Content(String name) {
            super(name);
        }

        @Override
        public void addPageElement(PageElement pageElement) {
            throw new UnsupportedOperationException("不支持此操作");
        }

        @Override
        public void rmPageElement(PageElement pageElement) {
            throw new UnsupportedOperationException("不支持此操作");
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException("不支持此操作");
        }

        @Override
        public void print(String placeholder) {
            System.out.println(placeholder + "──" + getName());
        }
        
    }
============================================================

创建树枝节点
树枝节点能够删除添加叶子或树枝。

============================================================
     public class Column extends PageElement {//栏目

        public Column(String name) {
            super(name);
        }

        @Override
        public void addPageElement(PageElement pageElement) {
            mPageElements.add(pageElement);
        }

        @Override
        public void rmPageElement(PageElement pageElement) {
            mPageElements.remove(pageElement);
        }

        @Override
        public void clear() {
            mPageElements.clear();
        }
        
        /**
         * @param placeholder 占位符
         */
        @Override
        public void print(String placeholder) {
            //利用递归来打印文件夹结构
            System.out.println(placeholder + "└──" + getName());
            Iterator<PageElement> i = mPageElements.iterator();
            while (i.hasNext()) {
                PageElement pageElement = i.next();
                pageElement.print(placeholder + "   ");
            }
        }

    }
============================================================

客户端测试:
============================================================
    public void test() {
        //创建网站根页面 root
        PageElement root = new Column("网站页面");
        //网站页面添加两个栏目:音乐,视屏;以及一个广告内容。
        PageElement music = new Column("音乐");
        PageElement video = new Column("视屏");
        PageElement ad = new Content("广告");
        root.addPageElement(music);
        root.addPageElement(video);
        root.addPageElement(ad);

        //音乐栏目添加两个子栏目:国语,粤语
        PageElement chineseMusic = new Column("国语");
        PageElement cantoneseMusic = new Column("粤语");
        music.addPageElement(chineseMusic);
        music.addPageElement(cantoneseMusic);

        //国语,粤语栏目添加具体内容
        chineseMusic.addPageElement(new Content("十年.mp3"));
        cantoneseMusic.addPageElement(new Content("明年今日.mp3"));

        //视频栏目添加具体内容
        video.addPageElement(new Content("唐伯虎点秋香.avi"));

        //打印整个页面的内容
        root.print("");
    }
============================================================

其他说明:
上面的例子可以看到叶子节点其实并不需要添加删除等方法,但由于叶子节点实际上是依赖了抽象组件角色。一方面,这遵循了依赖倒置原则——依赖抽象,而不依赖具体实现

同时,也保证了叶子节点跟树枝节点具体相同的结构,即他们具有同样的方法接口,能够让客户端以一致的方式去处理单个对象和组合对象。

但另一方,这违反了单一职责原则接口隔离原则,让 叶子节点继承了它本不应该有的方法,并且不太优雅的抛出了 UnsupportedOperationException 。这实际叫透明的组合模式

安全的组合模式

另外一种组合模式叫安全的组合模式。这种模式客户端在使用的时候必须依赖具体的实现,这违反了依赖倒置原则,但遵循了单一职责原则接口隔离原则

实现

============================================================
    public abstract class PageElement {//页面
        private String name;

        public PageElement(String name) {
            this.name = name;
        }

        //抽象组件角色去掉增删等接口

        public abstract void print(String placeholder);

        public String getName() {
            return name;
        }
    }
    
    public class Content extends PageElement {//具体内容,只专注自己的职责

        public Content(String name) {
            super(name);
        }
        
        @Override
        public void print(String placeholder) {
            System.out.println(placeholder + "──" + getName());
        }
    }
    
    public class Column extends PageElement {//栏目
        private List<PageElement> mPageElements = new ArrayList<>();//用来保存页面元素

        public Column(String name) {
            super(name);
        }

        public void addPageElement(PageElement pageElement) {
            mPageElements.add(pageElement);
        }

        public void rmPageElement(PageElement pageElement) {
            mPageElements.remove(pageElement);
        }

        public void clear() {
            mPageElements.clear();
        }

        @Override
        public void print(String placeholder) {
            System.out.println(placeholder + "└──" + getName());
            Iterator<PageElement> i = mPageElements.iterator();
            while (i.hasNext()) {
                PageElement pageElement = i.next();
                pageElement.print(placeholder + "   ");
            }
        }

    }
    
    public void test() {//客户端测试方法
        //依赖具体的实现类Column
        Column root = new Column("网站页面");
       
        Column music = new Column("音乐");
        Column video = new Column("视屏");
        PageElement ad = new Content("广告");
        root.addPageElement(music);
        root.addPageElement(video);
        root.addPageElement(ad);

        Column chineseMusic = new Column("国语");
        Column cantoneseMusic = new Column("粤语");
        music.addPageElement(chineseMusic);
        music.addPageElement(cantoneseMusic);

        chineseMusic.addPageElement(new Content("十年.mp3"));
        cantoneseMusic.addPageElement(new Content("明年今日.mp3"));

        video.addPageElement(new Content("唐伯虎点秋香.avi"));

        root.print("");
    }
============================================================

对比

安全的组合模式将职责区分开来放在不同的接口中,这样一来,设计上就比较安全,也遵循了单一职责原则和接口隔离原则,但是也让客户端必须依赖于具体的实现;透明的组合模式,以违反单一职责原则和接口隔离原则来换取透明性,但遵循依赖倒置原则,客户端可以直接依赖于抽象组件即可,将叶子和树枝一视同仁,也就是说,一个元素究竟是枝干节点还是叶子节点,对客户端是透明的。
  
一方面,我们写代码时应该遵循各种设计原则,但实际上,有些设计模式原则在使用时会发生冲突,这就需要我们根据实际情况去衡量做出取舍,适合自己的才是最好的。


Android中的源码分析

Android源码中,ViewGroup 和 View就是典型的组合模式。

View类

View相当与叶子节点,里面没有添加删除 View 等操作。

============================================================

    public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
        //具体代码略
    }

============================================================

ViewGroup类

ViewGroup实际上是View的子类,同时ViewGroup中实现了添加删除View等操作,因此可以作为容器存放view。

============================================================
    public abstract class ViewGroup extends android.view.View implements ViewParent, ViewManager {//继承View
        @Override
        public void addView(View child, android.view.ViewGroup.LayoutParams params) {//添加view
            //具体实现代码略
        }

        @Override
        public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {//更新view 
            //具体实现代码略
        }

        @Override
        public void removeView(View view) {//移除view
            //具体实现代码略
        }

        //其他代码略
    }
============================================================


ViewManager接口

实际上ViewGroup中的了添加删除View是实现了ViewManager接口中的方法:

============================================================
public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
============================================================


9.4 其他

可以看出ViewGroup 和 View 使用的是安全的组合模式,而不是透明的组合模式。

Reference:

https://www.jianshu.com/p/0580301d141d

Code Deodorant

Reference

https://refactoring.com/catalog/

https://www.jyt0532.com/toc/refactoring/

1. Long Function

Extract Function

Replace Temp with Query

Introduce Parameter Object

Preserve Whole Object

Replace Function with Command

Decompose Conditional

Replace Conditional with Polymorphism

Split Loop (重構與效能) 有時候為了讓程式碼更容易維護, 犧牲效能

2. Large Class
3. Long Parameter List

4. Data Clumps

5. Primitive Obsession

Replace Type Code with Subclasses

Replace Conditional with Polymorphism

Extract Class

Introduce Parameter Object

6. Repeated Switch

Replace Conditional with Polymorphism

Remove Flag Argument

7. Temporary Field

Extract Class

8. Alternative Classes with Different Interfaces

Move Function

Extract Superclass

9. Refused Bequest

Replace Superclass with Delegate (延伸)

10. Divergent Change

Move Function

Extract Function

Extract Class

11. Shotgun Surgery

Move Function

Combine Functions into Class

Combine Function into Transform 

Split Phase

Inline Function

12. Parallel Inheritance Hierarchies

Move Method

Move Field

13. Duplicate Code

Extract Function

14. Lazy Class

Inline Function

Inline Class

Collapse Hierarchy

15. Speculative Generality

Inline Function

Inline Class 

Change Function Declaration

16. Data Class

Remove Setting Method (this value can't change)

Move Function

Extract Function

Split Phase

17. Dead code

Remove Unreachable Code

18. Feature Envy

Move Function

Extract Function

19. Message Chains

Hide Delegate

Extract Function

Move Function

20. Middle Man

Inline Function

Replace Superclass with Delegate

Replace Subclass with Delegate

21. Inappropriate Intimacy

Move Field

Extract Class

Hide Delegate

Replace Inheritance with Delegation (https://learnku.com/docs/99-software-pattern/delegation-pattern/12018)

22. Comments

Extract Function

Change Function Declaration

23. Global Data

24. Mutable Data

Encapsulate Variable

Extract Function 

Remove Setting Method (this value can't change)

25. Mysterious Name

Change Function Declaration

26. Incomplete Library Class 

Move Method

27. Loops

28. Insider Trading

Move Function

Move Field

Replace Subclass with Delegate




Sunday, January 24, 2021

Android Adapter Pattern

 

定义

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

角色说明:
  • Adapter(适配器接口):即目标角色,定义把其他类转换为何种接口,也就是我们期望的接口。
  • Adaptee(被适配角色):即源角色,一般是已存在的类,需要适配新的接口。
  • ConcreteAdapter(具体适配器):实现适配器接口,把源角色接口转换为目标角色期望的接口。
5.5 说明:

类适配器模式只要通过继承源目标类来实现,无需持有源目标对象。

5.6 对象适配器模式与类适配器模式比较
  • 类适配器采用了继承的方式来实现;而对象适配器是通过传递对象来实现,这是一种组合的方式。
  • 类适配器由于采用了继承,可以重写父类的方法;对象适配器则不能修改对象本身的方法等。
  • 适配器通过继承都获得了父类的方法,客户端使用时都会把这些方法暴露出去,增加了一定的使用成本;对象适配器则不会。
  • 类适配器只能适配他的父类,这个父类的其他子类都不能适配到;而对象适配器可以适配不同的对象,只要这个对象的类型是同样的。
  • 类适配器不需要额外的引用;对象适配器需要额外的引用来保存对象。

总的来说,使用对象适配器比较好。当然具体问题具体分析。

6. 应用场景

  • 当想使用一个已经存在的类,但它的接口不符合需求时。
  • 当想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。

7. 优点

  • 提高了类的复用性,适配器能让一个类有更广泛的用途。
  • 提高了灵活性,更换适配器就能达到不同的效果。不用时也可以随时删掉适配器,对原系统没影响。
  • 符合开放封闭原则,不用修改原有代码。没有任何关系的类通过增加适配器就能联系在一起。

8. 缺点

  • 过多的使用适配器,会让系统非常零乱,不易整体进行把握。明明调用A接口,却被适配成B接口。

9. Android中的源码分析

说到适配器,ListViewRecyclerView就再熟悉不过了,ListView现在应该很少用了吧,这里就以RecyclerView来分析。

LayoutManager 用來管理版面配置

官方提供了三種總類

LinearLayoutManager 此類別會將項目 (item) 顯示在垂直或水平的清單中。

GridLayoutManager 此類別會將項目 (item) 顯示於網格中。

StaggeredGridLayoutManager 此類別會將項目 (item) 顯示於交錯網格中。

Adapter 是資料與 RecyclerView 之間的橋樑

使用 RecyclerView.Adapter 時必須實作 RecyclerView.ViewHolder 管理畫面 (ListView 也可設計 ViewHolder 來管以畫面,但沒有強制規定)




9.1 RecyclerView的使用

先来一个RecyclerView的简单使用例子:

Article.java

===========================================================

package com.little.testrecyclerveiw;
public class Article {
    private String title;
    private String subtitle;
    public Article() {
        title = "This is title.";
        subtitle = "This is subtitle.";
    }
    public Article(String title, String subtitle) {
        this.title = title;
        this.subtitle = subtitle;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getSubtitle() {
        return subtitle;
    }
    public void setSubtitle(String subtitle) {
        this.subtitle = subtitle;
    }
}

===========================================================

這個是清單的每一欄的版面配置,在 ArticleAdapter 中會利用 LayoutInflater 轉成 View。

Item 布局文件 item.xml

===========================================================

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:text="Title"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <TextView
        android:id="@+id/subtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Subtitle"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="@+id/title"
        app:layout_constraintTop_toBottomOf="@+id/title"/>
</android.support.constraint.ConstraintLayout>

===========================================================

Adapter代码

RecyclerView.ViewHolder and RecyclerView.Adapter

繼承 RecyclerView.Adapter 必須在泛型內放入一個繼承 RecyclerView.ViewHolder 的物件,此範例將 ViewHolder 宣告為 MyAdapter 的內部類別,而 ViewHolder 通常用以設定畫面元件。

另外,繼承 RecyclerView.Adapter 還需 Override 三個方法

onCreateViewHolder:初始化畫面元件。

onBindViewHolder:設定畫面元件內容。

getItemCount:取得資料 item 總數。

ArticleAdapter.java

===========================================================

package com.little.testrecyclerveiw;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
/**
 * Created by sarah on 08/12/2017.
 */
public class ArticleAdapter extends RecyclerView.Adapter<ArticleAdapter.ViewHolder> {
    private List<Article> articles;
    public ArticleAdapter(List<Article> articles) {
        this.articles = articles; //初始化数据
    }
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         // 实例化要展示的view布局
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,parent,false);
        ViewHolder viewHolder = new ViewHolder(itemView);
        return viewHolder;
    }
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Article article = articles.get(position);
        holder.bind(article);
    }
    @Override
    public int getItemCount() {
        return articles.size();
    }
    public static class ViewHolder extends RecyclerView.ViewHolder{
        private final TextView subtitle;
        private final TextView title;
        public ViewHolder(View itemView) {
            super(itemView);
            title = (TextView)itemView.findViewById(R.id.title);
            subtitle = (TextView)itemView.findViewById(R.id.subtitle);
        }
        public void bind(Article article){
            title.setText(article.getTitle());
            subtitle.setText(article.getSubtitle());
        }
    }
}

===========================================================

Activity布局文件 activity_main.xml

===========================================================

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.little.testrecyclerveiw.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
===========================================================

程式一開始先放入一百個 Article 近 List 中,之後再交給 ArticleAdapter 將資料顯示到 RecyclerView 中。

MainActivity.java

===========================================================
package com.little.testrecyclerveiw;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<Article> articles = new ArrayList<>();

        for (int i = 0;i<100;i++){
            Article article =new Article();
            article.setTitle("This is title #"+ i);
            article.setSubtitle("This is subtitle #"+ i);
            articles.add(article);
        }

        RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        ArticleAdapter adapter = new ArticleAdapter(articles);
        recyclerView.setAdapter(adapter);
    }
}

===========================================================


9.2 总结

从上面的例子可以看到,使用RecyclerView时我们要传递一个Adapter进去,然后通过复写Adapter中的onCreateViewHolder()onBindViewHolder()getItemCount()等方法以及内部类ViewHolder。通过不同的Adapter,我们能够实现不同的布局。

上面的MyAdapter是继承RecyclerView.Adapter这个类的,而Adapter实际上是RecyclerView的内部类。这里的Adapter就充当了适配器接口,即目标角色,RecyclerView内部通过Adapter获取它需要的接口和数据等。至于MyAdapter,就是具体的适配器,通过它把不同的布局跟RecyclerView联系起来。最后就是被适配角色,即源角色,这里就是各种不同的item布局。

为什么Android要这么设计呢,想想在一个列表中,我们要展示的布局可能是简单的,也可能是超复杂的,这个布局是未知的,是根据要展示的数据集来决定的;通过适配器模式,就能够把不同的东西都可以转化成同样的接口,系统处理起来就有套路了,同时也无需关心各种千变万化的布局

Activity: Android RecyclerView Load More, Endless Scrolling

Fragment



Reference:
https://www.jianshu.com/p/31686bf8f9a2
https://openclassrooms.com/en/courses/4788266-integrate-remote-data-into-your-app/5293951-implement-your-first-recyclerview
https://www.andreasjakl.com/kotlin-recyclerview-for-high-performance-lists-in-android/

n8n index

 【n8n免費本地端部署】Windows版|程式安裝x指令大補帖  【一鍵安裝 n8n】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能