Tuesday, January 5, 2021

Abstract Factory Pattern

5.应用场景

生产多个产品组合的对象时。

6.优点

代码解耦,创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建。

7.缺点

如果增加新的产品,则修改抽象工厂和所有的具体工厂,违反了开放封闭原则

 Android 中的 ThreadFactory 就是使用了工厂方法模式来生成线程的,线程就是 ThreadFactory 的产品。

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

    //抽象产品:Runnable

    public interface Runnable {

        public abstract void run();

    }

    //具体产品:Thread

    public class Thread implements Runnable {

        //构造方法

        public Thread(Runnable target, String name) {

            init(null, target, name, 0);

        }

        

        @Override

        //实现抽象产品的抽象方法

        public void run() {

            if (target != null) {

                target.run();

            }

        }

        //其他代码略

    }

    

    

    //抽象工厂:ThreadFactory

    public interface ThreadFactory {

        Thread newThread(Runnable r);

    }

    //具体工厂:AsyncTask中的实现

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {

        private final AtomicInteger mCount = new AtomicInteger(1);

        

        //实现抽象工厂的抽象方法

        public Thread newThread(Runnable r) {

            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());//返回Thread这个产品

        }

    };

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

https://evanchen76.medium.com/design-pattern-factory-method-165724c70532


Factory Pattern

应用场景

  • 生成复杂对象时,无需知道具体类名,只需知道相应的工厂方法即可。

优点

  • 符合开放封闭原则。新增产品时,只需增加相应的具体产品类和相应的工厂子类即可。
  • 符合单一职责原则。每个具体工厂类只负责创建对应的产品。

缺点

  • 一个具体工厂只能创建一种具体产品。
  • 增加新产品时,还需增加相应的工厂类,系统类的个数将成对增加,增加了系统的复杂度和性能开销。
  • 引入的抽象类也会导致类结构的复杂化。

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

public abstract class Product{

    public abstract void method();

public class ConcreteProductA extends Prodect{

    public void method(){

        System.out.println("我是产品A!");

    }

}

public class ConcreteProductB extends Prodect{

    public void method(){

        System.out.println("我是产品B!");

    }

}

public  abstract class Factory{

    public abstract Product createProduct();

}

public class MyFactory extends Factory{


    public Product createProduct(){

        return new ConcreteProductA();

    }

}

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

还可以通过传参的方式,让 MyFactory 的 createProduct 方法根据传入的参数决定是创建ConcreteProductA还是ConcreteProductB

Android example:

在 getSystemService 方法中就是用到了工厂模式,他就是根据传入的参数决定创建哪个对象,当然了,由于返回的都是以单例模式存在的对象,因此不用new了,直接把单例返回就好。

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

public Object getSystemService(String name) {

    if (getBaseContext() == null) {

        throw new IllegalStateException("System services not available to Activities before onCreate()");

    }

    if (WINDOW_SERVICE.equals(name)) {

         return mWindowManager;

    } else if (SEARCH_SERVICE.equals(name)) {

        ensureSearchManager();

        return mSearchManager;

    }

    return super.getSystemService(name);

  }

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

Reference:

https://juejin.cn/post/6844903474639929357

Monday, January 4, 2021

Prototype Pattern

 原型模式的使用時機

當今天想要複製一個模型裡面的屬性時

並且複製的是原本物件的全部屬性

當今天new可能會造成危害時 

Prototype 使用時機 & 優缺點

有些時候建立物件是非常耗時的,做了很多 IO 操作(DB, file, Api and parse json)才拿到所需要的物件。 

在需要建立類似物件時,會希望耗時操作越少越好,使用 Prototype 就可以減少這類型的消耗。

多執行緒或跨類別操作時可以藉由 Prototype 來避免共享變數,共享變數會造成不可預期的資料修改或是 Deadlock 。 

雖然在原書中該 pattern 沒有限制物件要是 immutable 的,但是在這種情形下搭配 immutable 物件會很適合的。

 缺點是實作 copy 比較繁瑣而且無聊,有些 library 或是語言本身可以幫助你完成這件事,像是 Java 的 Lombok 可以藉由 annotation 自動程式碼,或是 kotlin 的 Arrow 可以生成 Lens (嚴格的來說這並不符合 Prototype Pattern 的定義,但是能夠獲得一樣的效果)。

免在繼承的類別上使用 copy(),尤其是子類別有 override 父類別行為的時候,這時候會因為不同的呼叫時機點而產生難以控制的行為(這裡說的父類別可能是具體類別或是抽象類別,但不包含 interface)。Effective Java 第 17 條這樣說:

 Design and document for inheritance or else prohibit it.

通常這時候你應該要考慮的不是如何安全的實作 copy(),而是考慮使用組合來取代繼承。如果真的需要在繼承的類別實作 copy() 怎麼辦呢?將實作放在子類別吧,抽象類別可以選擇不實作。

应用场景

  • 如果初始化一个类时需要耗费较多的资源,比如数据、硬件等等,可以使用原型拷贝来避免这些消耗。
  • 通过new创建一个新对象时如果需要非常繁琐的数据准备或者访问权限,那么也可以使用原型模式。
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以拷贝多个对象供调用者使用,即保护性拷贝。

优点

  • 可以解决复杂对象创建时消耗过多的问题,在某些场景下提升创建对象的效率。
  • 保护性拷贝,可以防止外部调用者对对象的修改,保证这个对象是只读的。

缺点

  • 拷贝对象时不会执行构造函数。
  • 有时需要考虑深拷贝和浅拷贝的问题



Reference:

https://ithelp.ithome.com.tw/articles/10221129

Android example

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

public class Intent implements Parcelable, Cloneable {

        //其他代码略

        @Override

        public Object clone() {

            return new Intent(this);//这里没有调用super.clone()来实现拷贝,而是直接通过new来创建

        }

        public Intent(Intent o) {

            this.mAction = o.mAction;

            this.mData = o.mData;

            this.mType = o.mType;

            this.mPackage = o.mPackage;

            this.mComponent = o.mComponent;

            this.mFlags = o.mFlags;

            this.mContentUserHint = o.mContentUserHint;

            if (o.mCategories != null) {

                this.mCategories = new ArraySet<String>(o.mCategories);

            }

            if (o.mExtras != null) {

                this.mExtras = new Bundle(o.mExtras);

            }

            if (o.mSourceBounds != null) {

                this.mSourceBounds = new Rect(o.mSourceBounds);

            }

            if (o.mSelector != null) {

                this.mSelector = new Intent(o.mSelector);

            }

            if (o.mClipData != null) {

                this.mClipData = new ClipData(o.mClipData);

            }

        }

    }

 Uri uri=Uri.parse("smsto:10086");

Intent shareIntent=new Intent(Intent.ACTION_SENDTO,uri);

Intent intent=(Intetn)shareIntent.clone();

startActivity(intent);

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


Builder Pattern

应用场景

  • 创建一些复杂的对象时,对象内部的构建过程存在复杂变化。
  • 相同的构建过程,不同的执行顺序,产生不同结果时。
  • 不同配置的构建对象,产生不同结果时。

优点

  • 封装性良好,隐藏内部构建细节。
  • 易于解耦,将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。
  • 易于扩展,具体的建造者类之间相互独立,增加新的具体建造者无需修改原有类库的代码。
  • 易于精确控制对象的创建,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。

缺点

  • 产生多余的Build对象以及Dirextor类。
  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

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

public class MyData{

    private int id;

    private String num; 

    public void Test(){

    } 

    public void setId(int id){

        this.id=id;

    }

    public void setNum(String num){

        this.num=num+"id";

    }

}

public class MyBuilder{

    private int id;

    private String num;

    public MyData build(){

        MyData d=new MyData();

        d.setId(id);

        d.setNum(num);

        return t;

    }

    public MyBuilder setId(int id){

        this.id=id;

        return this;

    }

    public MyBuilder setNum(String num){

        this.num=num;

        return this;

    }

}

public class Test{

    public static void  main(String[] args){

        MyData d=new MyBuilder().setId(10).setNum("hc").build();

    }

}

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

Android example:

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

AlertDialog.Builer builder=new AlertDialog.Builder(context);

builder.setIcon(R.drawable.icon)

    .setTitle("title")

    .setMessage("message")

    .setPositiveButton("Button1", 

        new DialogInterface.OnclickListener(){

            public void onClick(DialogInterface dialog,int whichButton){

                setTitle("click");

            }   

        })

    .create()

    .show();

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

Singleton Pattern

应用场景

  • 频繁访问数据库或文件的对象。
  • 工具类对象;
  • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;

优点

  • 内存中只存在一个对象,节省了系统资源。
  • 避免对资源的多重占用,例如一个文件操作,由于只有一个实例存在内存中,避免对同一资源文件的同时操作。

缺点

  • 获取对象时不能用new
  • 单例对象如果持有Context,那么很容易引发内存泄露。
  • 单例模式一般没有接口,扩展很困难,若要扩展,只能修改代码来实现。

Single Thread

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

 public class Singleton{

    private static Singleton instance;

    private Singleton(){};

    public static Singleton getInstance(){

        if(instance==null)

            instance=new Singleton();

         return instatnce;

    }

}

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

Multi Thread

第一次判断是为了避免不必要的同步,第二次判断是确保在此之前没有其他线程进入到sychronized块创建了新实例

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

public class Singleton{

    private static Singleton instance;

    

    private Singleton(){};

    public static Singleton getInstance(){

        if(instance==null){

            sychronized(Singleton.class){

                if(instance==null)

                    instance=new Singleton();

            }

        }

        return instatnce;

    }

}

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

这段代码看上去非常完美,但是,,,却有隐患!问题出现在哪呢?主要是在instance=new Singleton();这段代码上。这段代码会编译成多条指令,大致上做了3件事:

(1)给Singleton实例分配内存

(2)调用Singleton()构造函数,初始化成员字段

(3)将instance对象指向分配的内存(此时instance就不是null啦~)


上面的(2)和(3)的顺序无法得到保证的,也就是说,JVM可能先初始化实例字段再把instance指向具体的内存实例,也可能先把instance指向内存实例再对实例进行初始化成员字段。其实一般情况下这两种方式肯定不会影响到程序的正确执行,但是会影响到上面的单例模式代码。在第二次判断instance是否为null时,可能已经在内存中进行实例化了,就差第(3)步了,可是我们的判断依然为true,导致这个线程在次new一个Singleton对象。

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

public class Singleton{

    private volatile static Singleton instance;

    

    private Singleton(){};

    public static Singleton getInstance(){

        if(instance==null){

            sychronized(Singleton.class){

                if(instance==null)

                    instance=new Singleton();

            }

        }

        return instatnce;

    }

}

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

相比前面的代码,这里只是对instance变量加了一个volatile关键字volatile关键字的作用是:线程每次使用到被volatile关键字修饰的变量时,都会去堆里拿最新的数据。换句话说,就是每次使用instance时,保证了instance是最新的。

Android example

WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);

Reference:
https://www.jianshu.com/p/d59c64480ed8

Sunday, January 3, 2021

Java reference

每種編程語言都有自己操作內存中元素的方式,例如在 C 和 C++ 裏是通過指針,而在 Java 中則是通過“引用”。

在 Java 中一切都被視為了對象,但是我們操作的標識符實際上是對象的一個引用(reference)

//創建一個引用,引用可以獨立存在,並不一定需要與一個對象關聯 String s;


通過將這個叫“引用”的標識符指向某個對象,之後便可以通過這個引用來實現操作對象了。

String str = new String("abc");

System.out.println(str.toString());


JDK1.2 之前,Java中的定義很傳統:如果 reference 類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱為這塊內存代表著一個引用。

Java 中的垃圾回收機制在判斷是否回收某個對象的時候,都需要依據“引用”這個概念。 在不同垃圾回收算法中,對引用的判斷方式有所不同:

引用計數法:為每個對象添加一個引用計數器,每當有一個引用指向它時,計數器就加1,當引用失效時,計數器就減1,當計數器為0時,則認為該對象可以被回收(目前在Java中已經棄用這種方式了)。

可達性分析算法:從一個被稱為 GC Roots 的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。 

 JDK1.2 之前,一個對象只有“已被引用”和"未被引用"兩種狀態,這將無法描述某些特殊情況下的對象,比如,當內存充足時需要保留,而內存緊張時才需要被拋棄的一類對象。

四種引用類型

所以在 JDK.1.2 之後,Java 對引用的概念進行了擴充,將引用分為了:

強引用(Strong Reference)

軟引用(Soft Reference)

弱引用(Weak Reference)

虛引用(Phantom Reference)4 種,這 4 種引用的強度依次減弱。

一,強引用

Java中默認聲明的就是強引用,比如:

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

Object obj = new Object(); //只要obj還指向Object對象,Object對象就不會被回收 

obj = null;  //手動置null

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

只要強引用存在,垃圾回收器將永遠不會回收被引用的對象,哪怕內存不足時,JVM也會直接拋出OutOfMemoryError,不會去回收。如果想中斷強引用與對象之間的聯系,可以顯示的將強引用賦值為null,這樣一來,JVM就可以適時的回收對象了

二,軟引用

軟引用是用來描述一些非必需但仍有用的對象。在內存足夠的時候,軟引用對象不會被回收,只有在內存不足時,系統則會回收軟引用對象,如果回收了軟引用對象之後仍然沒有足夠的內存,才會拋出內存溢出異常。

這種特性常常被用來實現緩存技術,比如網頁緩存,圖片緩存等。

在 JDK1.2 之後,用java.lang.ref.SoftReference類來表示軟引用。

下面以一個例子來進一步說明強引用和軟引用的區別: 在運行下面的Java代碼之前,需要先配置參數 -Xms2M -Xmx3M,將 JVM 的初始內存設為2M,最大可用內存為 3M。

首先先來測試一下強引用,在限制了 JVM 內存的前提下,下面的代碼運行正常

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

public class TestOOM {

    public static void main(String[] args) {

         testStrongReference();

    }

    private static void testStrongReference() {

        // 當 new byte為 1M 時,程序運行正常

        byte[] buff = new byte[1024 * 1024 * 1];

    }

}

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

但是如果我們將

byte[] buff = new byte[1024 * 1024 * 1];

換為創建一個大小為 2M 的字節數組

byte[] buff = new byte[1024 * 1024 * 2];

則內存不夠使用,程序直接報錯,強引用並不會被回收


接著來看一下軟引用會有什麽不一樣,在下面的示例中連續創建了 10 個大小為 1M 的字節數組,並賦值給了軟引用,然後循環遍歷將這些對象打印出來。

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

public class TestOOM {

    private static List<Object> list = new ArrayList<>();

    public static void main(String[] args) {

         testSoftReference();

    }

    private static void testSoftReference() {

        for (int i = 0; i < 10; i++) {

            byte[] buff = new byte[1024 * 1024];

            SoftReference<byte[]> sr = new SoftReference<>(buff);

            list.add(sr);

        }

        System.gc(); //主動通知垃圾回收

        for(int i=0; i < list.size(); i++){

            Object obj = ((SoftReference) list.get(i)).get();

            System.out.println(obj);

        }

    }

}

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


我們發現無論循環創建多少個軟引用對象,打印結果總是只有最後一個對象被保留,其他的obj全都被置空回收了。

這裏就說明了在內存不足的情況下,軟引用將會被自動回收。

值得註意的一點 , 即使有 byte[] buff 引用指向對象, 且 buff 是一個strong reference, 但是 SoftReference sr 指向的對象仍然被回收了,這是因為Java的編譯器發現了在之後的代碼中, buff 已經沒有被使用了, 所以自動進行了優化。

如果我們將上面示例稍微修改一下

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

    private static void testSoftReference() {

        byte[] buff = null;

        for (int i = 0; i < 10; i++) {

            buff = new byte[1024 * 1024];

            SoftReference<byte[]> sr = new SoftReference<>(buff);

            list.add(sr);

        }

        System.gc(); //主動通知垃圾回收

        for(int i=0; i < list.size(); i++){

            Object obj = ((SoftReference) list.get(i)).get();

            System.out.println(obj);

        }

        System.out.println("buff: " + buff.toString());

    }

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

則 buff 會因為強引用的存在,而無法被垃圾回收,從而拋出OOM的錯誤。


如果一個對象惟一剩下的引用是軟引用,那麽該對象是軟可及的(softly reachable)。垃圾收集器並不像其收集弱可及的對象一樣盡量地收集軟可及的對象,相反,它只在真正 “需要” 內存時才收集軟可及的對象。

三,弱引用

弱引用的引用強度比軟引用要更弱一些,無論內存是否足夠,只要 JVM 開始進行垃圾回收,那些被弱引用關聯的對象都會被回收。

在 JDK1.2 之後,用 java.lang.ref.WeakReference 來表示弱引用。 我們以與軟引用同樣的方式來測試一下弱引用:

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

    private static void testWeakReference() {

        for (int i = 0; i < 10; i++) {

            byte[] buff = new byte[1024 * 1024];

            WeakReference<byte[]> sr = new WeakReference<>(buff);

            list.add(sr);

        }

        System.gc(); //主動通知垃圾回收

        for(int i=0; i < list.size(); i++){

            Object obj = ((WeakReference) list.get(i)).get();

            System.out.println(obj);

        }

    }

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


可以發現所有被弱引用關聯的對象都被垃圾回收了。

四,虛引用

虛引用是最弱的一種引用關系,如果一個對象僅持有虛引用,那麽它就和沒有任何引用一樣,它隨時可能會被回收,在 JDK1.2 之後,用 PhantomReference 類來表示,通過查看這個類的源碼,發現它只有一個構造函數和一個 get() 方法,而且它的 get() 方法僅僅是返回一個null,也就是說將永遠無法通過虛引用來獲取對象,虛引用必須要和 ReferenceQueue 引用隊列一起使用。

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

public class PhantomReference<T> extends Reference<T> {

    /**

     * Returns this reference object‘s referent.  Because the referent of a

     * phantom reference is always inaccessible, this method always returns

     * <code>null</code>.

     *

     * @return  <code>null</code>

     */

    public T get() {

        return null;

    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {

        super(referent, q);

    }

}

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

那麽傳入它的構造方法中的 ReferenceQueue 又是如何使用的呢?

引用隊列可以與軟引用、弱引用以及虛引用一起配合使用,當垃圾回收器準備回收一個對象時,如果發現它還有引用,那麽就會在回收對象之前,把這個引用加入到與之關聯的引用隊列中去。程序可以通過判斷引用隊列中是否已經加入了引用,來判斷被引用的對象是否將要被垃圾回收,這樣就可以在對象被回收之前采取一些必要的措施。與軟引用、弱引用不同,虛引用必須和引用隊列一起使用。

參考

https://www.geeksforgeeks.org/types-references-java/

https://www.itread01.com/content/1537628537.html

Android Thread

[Android] Thread#1 WorkThread to MainThread by Handler

[Android] Thread#2 MainThread to WorkThread by Handler

[Android] Thread#3 WorkerThread的suspend與resume

n8n index

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