2017年10月12日 星期四

[Design Pattern] 獨體模式 (Singleton Pattern)

程式設計的時後,有些物件只能被實體化(new)一次
像是登錄(registry)物件、偏好設定物件等等
這些系統資訊的物件,常被其它的物件實體化取得資訊
在每個物件要取得資訊的時後都要先new一次才能用get()方法取得資訊
如果這個物件在很多處都有用到的話
那就會被實體化(new)很多次,可能會有很多問題產生
如:程式行為異常、資源使用過量或是不一致的問題

那要如何避免這樣問題呢?
工程師之間約定好只能實體化一次 : 這不用說問題有多大了XD
全域變數 : 在一開頭就要先建立好物件,不論是否會用到這個物件,這樣會造成資源的浪費
獨體化 : 可以在有使用到的時後才建立物件,有效的利用資源避免浪費

獨體化是利用靜態類別變數、靜態方法以及一些存取的修飾,來達到保証物件只被實體化一次

獨體設計根據不同問題的適用性有三種設計方法

  • 同步化getInstance()
public class Singleton {
  private static Singleton uniqueInstance;

  private Singleton(){}
  
  public static synchronized Singleton getInstance(){
    if (uniqueInstance == null){
      uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }
}

synchronized 關鍵字的作用就是同步化
會確認所有呼叫getInstance()都結束後才會繼續執行接下來的程式
這可以避免A物件正在建立Singleton的空窗期中,有其它物件正在檢查Singleton是否被建立
造成不一致的問題
但如果很頻繁會呼叫getInstance(),每次都要等待後才會處理
選用這個方法這樣會降低效率
  • 率先實體化(Eagerly instants)
public class Singleton {
  private static Singleton uniqueInstance = new Singleton();

  private Singleton(){}

  public static Singleton getInstance(){
    return uniqueInstance;
  }
}

在一開始進入Singleton類別的時後就先實體化類別,無論是否真的有使用到
如果確定一定要有一個Singleton類別,用這個方法也不錯。

  • 雙重檢查上鎖(Double-checked locking)
public class Singleton {
  private volatile static Singleton uniqueInstance;
  
  private Singleton(){}

  public static Singleton getInstance(){
    if (uniqueInstance == null){
      synchronized (Singleton.class) {
        if (uniqueInstance == null){
          uniqueInstance = new Singleton();
        }
      }
    }
    return uniqueInstance;
  }
}

如果沒有效能上的考量就不需要用這個方法,不然有種殺雞用牛刀的感覺
用這個方法要注意要用JAVA 2的版本是5以上唷


2017年10月5日 星期四

[Design Pattern] 觀察者模式 (Observer Pattern)

「當餐廳更新菜單時,有申請要收到更新通知的會員,就會收到第一手消息」
一對多就是觀察者模式的主軸概念
觀察者模式有二個角色
  1. 主題 - Subject
  2. 觀察者 - Observer
依上面的範例來看
Subject 就是餐廳,Observer 就是會員,餐廳更新的時後就發通知給會員
那我們現在再把這個概念想的完整一點

先從Subject的角度來看,Subject裡要有什麼樣的功能呢?

通知不是發給所有的會員,是只發給「有申請要收到更新通知的會員」
那是不是就要有「註冊申請收到更新通知」的功能給會員申請使用呢
就是 RegisterObserver (我想申請當一個觀察者)

當然有註冊也要有取消再收到通知的功能
就是 RemoveObserver (我覺得煩 不想再收到通知了)

那是不是還少了通知的功能呢
就是 NotifyObserver (通知會員囉)

從Observer的角度來看,Object裡要有什麼功能呢?

收到從餐廳發來的更新會送到哪裡呢?會員的信箱、電話、地址等等。
這麼多個方法可以收到餐廳的更新通知,那就統一一個名稱吧
就是 Update() (因為收到更新後會員對餐廳的菜單就會進行資料更新呀)

從實作的角度來看,我們該怎麼實作觀察者模式

首先不管什麼模式核心理念都是物件與物件之間要鬆綁,不需要知道彼此的細節
鬆綁就是不要物件與物件之間直接呼叫,透過中間人來呼叫也就是介面

Java程式碼:
1. 建立觀察者的介面
public interface ClientObserver {
  public void update();
}

2. 建立主題的介面
public interface NewsPaperSubject {
  public void registerObserver(ClientObserver o);
  public void removeObserver(ClientObserver o);
  public void notifyObserver();
}

3. 建立主體物件
public class NewsPaper implements NewsPaperSubject {
  ArrayList clients = new ArrayList<>(); // storage observer list

  @Override
  public void registerObserver(ClientObserver o){
    clients.add(o);
  }

  @Override
  public void removeObserver(ClientObserver o){
    int i = clients.indexOf(o);
    if (i >=1) {
      clients.remove(i);
    }
  }

  @Override
  public void notifyObserver(){
     for(ClientObserver client : clients){
       client.update();
     }
  }
}

4. 建立觀察者物件
public class ATeamObserver implement ClientObserver{
  NewspaperSubject subject;

  public ATeamObserver(NewspaperSubject newsPaperSubject){
    subject = newsPaperSubject;
    subject.registerObserver(this);
  }

  @Override
  public void update(){
    //TODO: update data
  }

  public void cancelObserver()(
    subject.removeObserver();
  }
}

如果有新的觀察者加入不需要修改既有的程式碼
只需要再建一個新的觀察者物件並實作ClientObserver

只要有register加入newsPaperSubject的觀察者,當newsPaperSubject呼叫notifyObserver
都會收到通知進入update(),就可以各自處理收到通知後的行為
這種一對多即時更新的模式,就適用Observer Pattern

2017年10月2日 星期一

設計模式 Design Pattern

程式設計師就像是一位翻譯官,負責人類和3C產品之間的溝通交流
將所有現實中的行為邏輯翻譯成電腦看的懂的語言
而現實生活中的語言表達,如果可以讓人清楚的明白意思,那就是個良好的表達
轉換到程式設計,如果可以讓人清楚的明白意思,那就是良好的程式碼

常有人會問寫程式難嗎?
我認為寫程式並不難,你可以用基本的if..else 條件判斷完成大部分的功能
只要有心人人都可以寫出一個程式,但要怎麼體現一個工程師的價值
就是他的程式碼的表達能力有多好
優秀的軟體工程師的程式碼,是可以讓其它工程師容易了解他的架構及功能
這就是所謂的易讀性,易讀性高的程式可以降低bug發生的機率和易維護修改

設計模式就像是一個表達的技巧,在對的時間用上對的技巧就可以達到事半功倍的成效
這些技巧都是技術高超的工程師集思廣義整理出來的
他們提取出認為可以提高程式的易讀性、修改性的程式架構
當然可以不一定要照著他們的方法做
除非你覺得你比他們威,如果沒有.. 那還是先照做一下吧XD

設計模式最為知名的應該就是GoF的23種模式
業界大多都照著GoF的模式開發
愈多人用表示這個模式的溝通性就愈高
所以我以GoF的模式以比較常見的幾種來學習模倣

  • 策略模式 (Strategy Pattern)
  • 觀察者模式 (Observer Pattern)
  • 裝飾者模式 (Decorator Pattern)
  • 工廠模式 (Factory Pattern)
  • 獨體模式 (Singleton Pattern)
  • 命令模式 (Command Pattern)
  • 轉接器和外觀模式 (Adapter Pattern and Facade Pattern)
  • 樣板方法模式 (Template Method Pattern)
  • 反覆器和合成模式 (Iterator and Composite Pattern)

之後會分享一下我的學習心得

2017年9月19日 星期二

[Android] 如何修改預設output 的APK 名稱

編譯專案後會產生output apk,預設名稱會是 "app-release.apk"
但在開發專案的時後常會需要根據 build type 不同有對應的名稱,例如 xxx-release.apk
當然可以每次build出apk後,再去修改名稱,當個勤勞的工程師
可是一天我們會build上好幾上次的apk,每次都要做更改,光想就覺得累
我們可以利用修改Gradle 幫我們把這個行為自動化

在這邊用比較複雜的名稱格式來當範例
將APK名稱格式修改成 myProject_dev_debug_1.3.6_131016_1047.apk.
專案名稱_Flavors_buildType_versionName_ddMMyy_HHmm
這些是我們會用到的資訊

  • flavor
  • buildType
  • version
  • date

app/build.gradle

android {

    ...

    buildTypes {
        release {
            minifyEnabled true
            ...
        }
        debug {
            minifyEnabled false
        }
    }

    productFlavors {
        prod {
            applicationId "com.feraguiba.myproject"
            versionCode 3
            versionName "1.2.0"
        }
        dev {
            applicationId "com.feraguiba.myproject.dev"
            versionCode 15
            versionName "1.3.6"
        }
    }

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def project = "myProject"
            def SEP = "_"
            def flavor = variant.productFlavors[0].name
            def buildType = variant.variantData.variantConfiguration.buildType.name
            def version = variant.versionName
            def date = new Date();
            def formattedDate = date.format('ddMMyy_HHmm')

            def newApkName = project + SEP + flavor + SEP + buildType + SEP + version + SEP + formattedDate + ".apk"

            output.outputFile = new File(output.outputFile.parent, newApkName)
        }
    }
}
reference :
https://stackoverflow.com/questions/28249036/app-release-apk-how-to-change-this-default-generated-apk-name/30332234#30332234

[Android] 用Command Line 執行APK 及 如何取得Package及Launch Activity name

在專案開發的時後,有時後會用到第三方的apk
但是沒辦法在裝置上直接執行它,因為apk被隱藏起來了
所以不會出現在Launch app的地方
那如果要Launch 隱藏app要怎麼做呢? 透過Command Line 執行apk


如何用Command Line 執行apk


指令結構 : adb shell am start -n package name/full activity name
$adb shell am start -n com.test.drm/com.test.drm.drmActivity


如何Command Line取得 Package Name 及 Launch Activity Name


  • 取得裝置內已安裝的Package Name
  • 指令結構 : adb shell pm list packages -f

    • -f : 一併列出apk檔案的存放位置
    • -s : 只列出系統套件(system package)
    • -3 : 只列出第三方套件 (3rd-party package)
    • FILTER : 只列出FILTER的套件名稱
    $ adb shell pm list packages -f

  • 取得 apk Launch Activity Name

    • 先用 adb shell pm list packages -f 取得apk 檔案的存放位置
    • 再執行 adb pull /apk檔案路徑/xxx.apk
    • 開啟Android Studio -> Build/Analyze APK...
    • 就可以查看AndroidManifest.xml

    2014年6月18日 星期三

    Object C 初體驗


    • 減號(或加號):代表函數、方法的開始
    例如C#方法的寫法是
    private void hello (boolean b_start){
        OOXX;
    }

    Object C的寫法是
    -(void)hello:(boolean)b_start{
        OOXX;
    }

    不過在Object C 是沒有private, public的區分,可都視為public
    而加號則是表示其它的函數可以直接調用這個類中的函數,而不用創建這個類的實例
    • 中括號[]
    中括號可視為要如何調用我們剛才寫的方法,通常在Object C 稱為[消息]。
    例如C#是可以這麼寫
    this.hello(true);

    Object C的寫法是
    [self hello:YES];

    • NS****
    當年賈伯斯被趕出蘋果時,所創立的公司Next Step,裡面一些開發包是很讓科學家喜歡的,這套函數庫也就是現在MAC OS 所使用的。
    而這套Function library 的開發團體比較自戀,所用的function name都是以Next Step的第一個字母命名NS****
    比較常見的就是
    NSLog
    NSString
    NSInteger
    NSURL
    NSImage

    問題:什麼是#import、@interface

    • #import
    這可以視為#include是一樣的。但最好是用#import,記住這個就好

    • @interface

    例如在C#寫一個抓孩子類的定義
    public class Kids : system
    {
        private string str_kidName = "My Kids";
        private string str_kidAge = "15";
        private bool bl_isCaughtkid ()
        {
            return true;
        }
    }

    用Object C表示:
    先寫一個Kid.h定義這個類

    @interface Kids:NSObject
    {
        NSString *str_kidName;
        NSString *str_kidAge;
    }
    -(BOOL)bl_isCaughtkid:;
    @end

    再寫一個Kid.m實現這個類

    #import "Kid.h"
    @implement Kids
    -(void) init:{
        str_kidName =@ "My Kids";
        str_kidAge = @"15";
    }

    -(BOOL)bl_isCaughtkid:{
        return YES;
    }
    @end

    問題:一個方法怎麼傳遞多個參數?
    一個方法可以包含多個參數,只是後面的參數都要有參數名稱。

    多個參數的寫法
    (方法的數據類型)函數名稱:(參數1數據類型)參數1數值名稱 參數2的名字:(參數2數據類型) 參數2數值名稱

    舉例、一個方法的定義

    -(void)setkids:(NSString *)myOldestKidName secondKid:(NSString *)mySecondOldestKidName thirdKid:(NSString *)myThirdOldestKidName

    一個方法的實現

    -(void)setkids:(NSString *)myOldestKidName secondKid:(NSString *)mySecondOldestKidName thirdKid:(NSString *)myThirdOldestKidName{
        大兒子名字 = myOldestKidName;
        二兒子名字 = mySecondOldestKidName;
        三兒子名字 = myThirdOldestKidName;
    }

    調用的時後

    Kids *mykids = [[Kids alloc] init];
    [mykids setkids:@"張大力" secondKid:@"張二力" thirdKid:@"張三力"];

    而如果你用c#寫這個方法,大致的寫法可能是
    public void setKids( string myOldestKidName, stringmySecondOldestKidName, stringmyThirdOldestKidName)
    {
    }
    調用的時候大概的寫法可能是:
    Kids myKids = new Kids();
    myKids.setKids (「張大力」「張二力」「張小力」);
    明白了吧?其實不怎麼難看懂。
    基本上,如果你能了解下面這段代碼的轉換關系,你Objective-C的語法也就懂了八成了:
    [[[MyClass allocinit:[foo bar]] autorelease];
    轉換成C#或者Java的語法也就是:
    MyClass.alloc().init(foo.bar()).autorelease();

    2012年3月26日 星期一

    CString的構造函數

    CString的構造函數
    CString( );
    例:CString csStr;

    CString( const CString& stringSrc );
    例:CString csStr("ABCDEF中文123456");
    CString csStr2(csStr);

    CString( TCHAR ch, int nRepeat = 1 );
    例:CString csStr('a',5);
    //csStr="aaaaa"

    CString( LPCTSTR lpch, int nLength );
    例:CString csStr("abcdef",3);
    //csStr="abc"

    CString( LPCWSTR lpsz );
    例:wchar_t s[]=L"abcdef";
    CString csStr(s);
    //csStr=L"abcdef"

    CString( const unsigned char* psz );
    例:const unsigned char s[]="abcdef";
    const unsigned char* sp=s;
    CString csStr(sp);
    //csStr="abcdef"

    CString( LPCSTR lpsz );
    例:CString csStr("abcdef");
    //csStr="abcdef"

    int GetLength( ) const;
    返回字符串的長度,不包含結尾的空字符。
    例:csStr="ABCDEF中文123456";
    printf("%d",csStr.GetLength()); //16

    void MakeReverse( );
    顛倒字符串的順序
    例:csStr="ABCDEF中文123456";
    csStr.MakeReverse();
    cout<//654321文中FEDCBA

    void MakeUpper( );
    將小寫字母轉換為大寫字母
    例:csStr="abcdef中文123456";
    csStr.MakeUpper();
    cout<//ABCDEF中文123456

    void MakeLower( );
    將大寫字母轉換為小寫字母
    例:csStr="ABCDEF中文123456";
    csStr.MakeLower();
    cout<//abcdef中文123456

    int Compare( LPCTSTR lpsz ) const;
    區分大小寫比較兩個字符串,相等時返回0,大於時返回1,小於時返回-1
    例:csStr="abcdef中文123456";
    csStr2="ABCDEF中文123456";
    cout< //0

    int CompareNoCase( LPCTSTR lpsz ) const;
    不區分大小寫比較兩個字符串,相等時返回0,大於時返回1,小於時返回-1
    例:csStr="abcdef中文123456";
    csStr2="ABCDEF中文123456";
    cout<//-1

    int Delete( int nIndex, int nCount = 1 )
    刪除字符,刪除從下標nIndex開始的nCount個字符
    例:csStr="ABCDEF";
    csStr.Delete(2,3);
    cout<// ABF
    //當nIndex過大,超出對像所在內存區域時,函數沒有任何操作。
    //當nIndex為負數時,從第一個字符開始刪除。
    //當nCount過大,導致刪除字符超出對像所在內存區域時,會發生無法預料的結果。
    //當nCount為負數時,函數沒有任何操作。

    int Insert( int nIndex, TCHAR ch )
    int Insert( int nIndex, LPCTSTR pstr )

    在下標為nIndex的位置,插入字符或字符串。返回插入後對象的長度
    例:csStr="abc";
    csStr.Insert(2,'x');
    cout< //abxc
    csStr="abc";
    csStr.Insert(2,"xyz");
    cout< //abxyzc
    //當nIndex為負數時,插入在對象開頭
    //當nIndex超出對象末尾時,插入在對象末尾

    int Remove( TCHAR ch );
    移除對象內的指定字符。返回移除的數目
    例:csStr="aabbaacc";
    csStr.Remove('a');
    cout< //bbcc

    int Replace( TCHAR chOld, TCHAR chNew );
    int Replace( LPCTSTR lpszOld, LPCTSTR lpszNew );

    替換字串
    例:csStr="abcdef";
    csStr.Replace('a','x');
    cout<//xbcdef
    csStr="abcdef";
    csStr.Replace("abc","xyz");
    cout<//xyzdef

    void TrimLeft( );
    void TrimLeft( TCHAR chTarget );
    void TrimLeft( LPCTSTR lpszTargets );

    從左刪除字符,被刪的字符與chTarget或lpszTargets匹配,一直刪到第一個不匹配的字符為止
    例:csStr="aaabaacdef";
    csStr.TrimLeft('a');
    cout<//baacdef
    csStr="aaabaacdef";
    csStr.TrimLeft("ab");
    cout< //cdef
    //無參數時刪除空格

    void TrimRight( );
    void TrimRight( TCHAR chTarget );
    void TrimRight( LPCTSTR lpszTargets );

    從右刪除字符,被刪的字符與chTarget或lpszTargets匹配,一直刪到第一個不匹配的字符為止
    例:csStr="abcdeaafaaa";
    csStr.TrimRight('a');
    cout<//abcdeaaf
    csStr="abcdeaafaaa";
    csStr.TrimRight("fa");
    cout<//abcde
    //無參數時刪除空格

    void Empty( );
    清空
    例:csStr="abcdef";
    csStr.Empty();
    printf("%d",csStr.GetLength()); //0

    BOOL IsEmpty( ) const;
    測試對象是否為空,為空時返回零,不為空時返回非零
    例:csStr="abc";
    cout<//0;
    csStr.Empty();
    cout<//1;

    int Find( TCHAR ch ) const;
    int Find( LPCTSTR lpszSub ) const;
    int Find( TCHAR ch, int nStart ) const;
    int Find( LPCTSTR pstr, int nStart ) const;

    查找字串,nStart為開始查找的位置。未找到匹配時返回-1,否則返回字串的開始位置
    例:csStr="abcdef";
    cout<//1
    cout<//3
    cout<//-1
    cout<//1
    cout<//-1
    cout<//3
    //當nStart超出對象末尾時,返回-1。
    //當nStart為負數時,返回-1。

    int FindOneOf( LPCTSTR lpszCharSet ) const;
    查找lpszCharSet中任意一個字符在CString對象中的匹配位置。未找到時返回-1,否則返回字串的開始位置
    例:csStr="abcdef";
    cout< //2

    CString SpanExcluding( LPCTSTR lpszCharSet ) const;
    返回對象中與lpszCharSet中任意匹配的第一個字符之前的子串
    例:csStr="abcdef";
    cout<//ab

    CString SpanIncluding( LPCTSTR lpszCharSet ) const;
    從對象中查找與lpszCharSe中任意字符不匹配的字符,並返回第一個不匹配字符之前的字串
    例:csStr="abcdef";
    cout<//abcd

    int ReverseFind( TCHAR ch ) const;
    從後向前查找第一個匹配,找到時返回下標。沒找到時返回-1
    例:csStr="abba";
    cout<//3

    void Format( LPCTSTR lpszFormat, ... );
    void Format( UINT nFormatID, ... );

    格式化對象,與C語言的sprintf函數用法相同
    例:csStr.Format("%d",13);
    cout<//13

    TCHAR GetAt( int nIndex ) const;
    返回下標為nIndex的字符,與字符串的[]用法相同
    例:csStr="abcdef";
    cout<//c
    //當nIndex為負數或超出對象末尾時,會發生無法預料的結果。

    void SetAt( int nIndex, TCHAR ch );
    給下標為nIndex的字符重新賦值
    例:csStr="abcdef";
    csStr.SetAt(2,'x');
    cout<//abxdef
    //當nIndex為負數或超出對象末尾時,會發生無法預料的結果。

    CString Left( int nCount ) const;
    從左取字串
    例:csStr="abcdef";
    cout< //abc
    //當nCount等於0時,返回空。
    //當nCount為負數時,返回空。
    //當nCount大於對象長度時,返回值與對象相同。

    CString Right( int nCount ) const;
    從右取字串
    例:csStr="abcdef";
    cout<//def
    //當nCount等於0時,返回空。
    //當nCount為負數時,返回空。
    //當nCount大於對象長度時,返回值與對象相同。

    CString Mid( int nFirst ) const;
    CString Mid( int nFirst, int nCount ) const;

    從中間開始取字串
    例:csStr="abcdef";
    cout<//cdef
    csStr="abcdef";
    cout<//cde
    //當nFirst為0和為負數時,從第一個字符開始取。
    //當nFirst等於對象末尾時,返回空字串。
    //當nFirst超出對象末尾時,會發生無法預料的結果。
    //當nCount超出對象末尾時,返回從nFirst開始一直到對象末尾的字串
    //當nCount為0和為負數時,返回空字串。

    LPTSTR GetBuffer( int nMinBufLength );
    申請新的空間,並返回指針
    例:csStr="abcde";
    LPTSTR pStr=csStr.GetBuffer(10);
    strcpy(pStr,"12345");
    csStr.ReleaseBuffer();
    pStr=NULL;
    cout< //12345
    //使用完GetBuffer後,必須使用ReleaseBuffer以更新對象內部數據,否則會發生無法預料的結果。

    void ReleaseBuffer( int nNewLength = -1 );
    使用GetBuffer後,必須使用ReleaseBuffer以更新對象內部數據
    例:csStr="abc";
    LPTSTR pStr=csStr.GetBuffer(10);
    strcpy(pStr,"12345");
    cout<//3(錯誤的用法)
    csStr.ReleaseBuffer();
    cout<//5(正確)
    pStr=NULL;
    //CString對象的任何方法都應在ReleaseBuffer之後調用

    LPTSTR GetBufferSetLength( int nNewLength );
    申請新的空間,並返回指針
    例:csStr="abc";
    csStr.GetBufferSetLength(20);
    cout<//abc
    count< //20;
    csStr.ReleaseBuffer();
    count<//3;
    //使用GetBufferSetLength後可以不必使用ReleaseBuffer。