如何建立一個對象(一)

這是一個開篇,不管博客仍是論壇,都一直潛水,享受各位做者帶來的分享,卻一直沒有什麼回饋。
從我建立博客幾年來,只有渺渺兩篇,仍是當時初學編程時的總結,由於總總緣由,這個也未堅持下去。幾年來一直想要動筆,卻一直找不到什麼契機,大概是怯於表達,或以爲積累不夠,或怠於堅持,我想歸根結底仍是太懶。
  java

1 建立對象實例

  最近一段時間,忽然發現建立對象頗有意思,這個雖然簡單,但每每容易被人忽視,起碼在此以前,我就從未以這種角度去總結過它。
  在此以前咱們要建立一個簡單的商店類(POJO):編程

#code 1
/**
 * 商店  
 */
public class Shop {   
    private int shopId;   
    private String shopName;  
    private String address;

    public int getShopId() {   return shopId;}
    public void setShopId(int shopId) {   this.shopId = shopId;}
    public String getShopName() {   return shopName;}
    public void setShopName(String shopName) {   this.shopName = shopName;}
    public String getAddress() {   return address;}
    public void setAddress(String address) {   this.address = address;}

    @Override
    public String toString() {   
        return "商店名稱:" + shopName + ";商店地址:" + address + "\r\n";
    }
}

1.1 構造器(Constructor)

  這能夠說是最簡單最多見的建立對象實例的方式,不少時候,咱們都會不由自主的這麼寫:c#

#code 1.1 part 1
//建立Shop的實例
Shop shop = new Shop();

  這得益於一個公有的Java類,若是沒有顯式聲明受保護或私有的無參構造方法,就默認一個公有的無參構造。
  這個寫法自己沒有任何問題,當咱們須要爲字段賦值的時候,咱們能夠用帶參構造的方式,爲對象屬性賦值:設計模式

#code 1.1 part 2
public Shop(String shopName, String address) {   
    this.shopName = shopName;   
    this.address = address;
}
public Shop(int shopId, String shopName, String address) {   
    this.shopId = shopId;   
    this.shopName = shopName;   
    this.address = address;
}

  這是一個簡單的例子,咱們日常在編碼的時候是否直接使用這種方式去建立對象的同時爲屬性賦值呢?想象一下吧,當這個Shop類複雜到多至幾十個屬性,你可能須要寫幾十個構造重載它才能知足需求。更況且重載也不能解決一切問題,譬如:緩存

#code 1.1 part 3
public Shop(String shopName) {   
    this.shopName = shopName;
}
public Shop(String address) {   
    this.address = address;
}

  代碼(#code 1.1 part 3)編譯階段就沒法經過,重載可不認爲這是兩個構造函數,這時候你的需求永遠沒法被知足,並且用這樣的方式不免會存在代碼失去控制難以閱讀,也會存在某一個細小的錯誤難以排查,好比你忘了爲某個屬性賦值或者顛倒了兩個一樣類型的屬性值。不過咱們還有其它的選擇,例如選擇JavaBean的方式爲對象屬性賦值,利用每一個屬性的setter方法:安全

#code 1.1 part 4
Shop shop = new Shop();
//爲shop實例字段賦值
shop.setShopName("XX商店");
shop.setAddress("XX省XX市XX區XX街道555號");

  做爲一個一直使用.Net的編碼者,我一直認爲JavaBean的寫法是比較繁瑣的,一樣建立對象,.Net能夠經過對象的初始化器完成:框架

#code 1.1 part 5
//定義Shop類
public class Shop
{
    public int ShopId{ get; set; }
    public String ShopName{ get; set; }
    public String Address{ get; set; }
}
//初始化器
Shop shop = new Shop(){
    ShopName="XX商店";
    Address="XX省XX市XX區XX街道555號";
};

1.2 反射(Reflector)

  固然,上述代碼均可以經過反射代替,下面是無參構造的反射實例:ide

#code 1.2 part 1
try {   
    Shop shop = (Shop) Class.forName("package.Shop").newInstance();   
    shop.setShopName("XX商店");   
    shop.setAddress("XX省XX市XX區XX街道555號");
    System.out.printf(shop.toString());
} catch (InstantiationException e) {   
    e.printStackTrace();
} catch (IllegalAccessException e) {   
    e.printStackTrace();
} catch (ClassNotFoundException e) {   
    e.printStackTrace();
}

  反射的本質是經過指定方式獲取元數據。這裏經過找到指定類,調用其無參構造獲得該實例。帶參構造也是如此:函數

#code 1.2 part 2
try {   
    Constructor<?>[] ctors = Class.forName("package.Shop").getConstructors();   
    Shop shop = null;   
    for (Constructor ctor : ctors) {      
        if (ctor.getParameterCount() == 2 &&
                ctor.toString().contains("package.Shop(java.lang.String,java.lang.String)")) {         
            shop = (Shop) ctor.newInstance("XX商店", "XX省XX市XX區XX街道555號");      
        }   
    }   
    if (null != shop) System.out.printf(shop.toString());
} catch (InstantiationException e) {   
    e.printStackTrace();
} catch (IllegalAccessException e) {   
    e.printStackTrace();
} catch (InvocationTargetException e) {   
    e.printStackTrace();
} catch (ClassNotFoundException e) {   
    e.printStackTrace();
}

  

1.3 靜態工廠方法(static factory method)

  靜態工廠方法,顧名思義,必定是靜態的,但卻不一樣於設計模式的工廠,這裏僅僅是返回一個類實例的靜態方法。既然是靜態方法,本質上和該類當中的其它靜態方法並沒有任何區別。
  咱們先經過一段java源碼示例簡單看下靜態工廠方法到底是什麼:性能

#code 1.3 part 1
// java.lang.Class
@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {    
    Class<?> caller = Reflection.getCallerClass();    
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

  是否是很熟悉?沒錯,這就是上述反射(代碼#code 1.2 part 1)中使用到的Class.forName()方法,且Class中forName()方法具備多個重載,返回參數類型的實例。再看一段源碼示例:

#code 1.3 part 2
public static String valueOf(Object obj) {    
    return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(int i) {    
    return Integer.toString(i);
}

  這是java.lang.String中的一段代碼,只截取兩個方法,在java.lang.String類中,重載了大約9個valueOf()方法,分別是各類參數類型以知足各類需求。這些方法都是返回一個java.lang.String實例。
  體會一下,那麼靜態工廠方法相比較於構造方法有哪些優點?
  首先,既然是方法,那麼命名就能提升代碼的可讀性,見名思義,一目瞭然,相反構造器必須和類名一致。
  其次,你沒必要在每次調用它的時候都建立一個新的對象。你可使用一個緩存的對象返回,或者一個不可變的對象。這避免了建立重複對象有利於提高程序性能。好比Boolean.valueOf()方法返回了一個Boolean對象的實例,不是true就是false。
  另外,只要你願意,靜態工廠方法的返回對象能夠是任何類型的對象,比較典型的例如java.util.Collections,這個類顯式聲明瞭一個私有構造不能讓其實例化,而後提供了多達三十幾個靜態工廠方法用於返回各類類型。所以,它提供了更多的編碼上的靈活性,一樣,因爲java語言特性的限制,構造器只能返回當前類的對象。
  服務提供者框架(Service Provider Framework),組成部分包括服務接口(Service Interface)、提供者註冊API(Provider Registration API)、服務訪問API(Service Access API)、服務提供者接口(Service Provider Interface)。而靜態工廠方法是完成它的基礎。例如JDBC,服務接口是Connection,服務提供者接口是Driver,DriverManager類當中registerDriver()方法是服務註冊API,getConnection()方法是服務訪問API。這些元素就組成了服務提供者框架。

#code 1.3 part 3
//服務接口
public interface Connection{
    //...這裏定義了例如commit()、close()等一系列接口
}
//服務提供者接口
public interface Driver {
}
//註冊提供者和獲取服務
public class DriverManager {
    public static synchronized void registerDriver(Driver driver){}
    public static Connection getConnection(String url,Properties info){}
}

  再看個例子,假設如今商店因規模擴張、經營方式改變,如今與多個供應商簽定協議,須要向多個供應商要貨,且自動完成配給。咱們知道,多個供應商和商店是徹底獨立的系統,它們的要貨流程未必一致,那如何將它們對接起來?無論這個場景是否合適,我麼如今就嘗試使用服務提供者框架來作。

#code 1.3 part 4
//首先是商店須要對外定義協議接口
public interface Service {   
    /**  要貨    */   
    boolean getGoods();
}
public interface Provider {   
    /**    獲取服務   */   
    Service newService();
}
//接下來定義一個容器註冊供應商
public class ServiceManager {   
    private ServiceManager() {   }   
    private static final Map<String, Provider> providers = new ConcurrentHashMap<>();   
    public static void registerProvider(String name, Provider p) {
        providers.put(name, p);   
    }   
    public static Service newInstance(String name) {      
        Provider p = providers.get(name);      
        if (null == p) {         
            throw new IllegalArgumentException("No provider registered with name:" + name);      
        }      
        return p.newService();   
    }
}
//接下來須要每一個供應商來實現這些接口,例如如今有A、B兩家供應商
//供應商A - 實現
public class ASupplierService implements Service {   
    @Override   
    public boolean getGoods() {      
        System.out.printf("如今是 A供應商 供貨流程...\r\n");      
        return true;  
    }
}
public class ASupplierProvider implements Provider {   
    static {      
        ServiceManager.registerProvider("A", new ASupplierProvider()); 
    }   
    @Override   
    public Service newService() {      
        return new ASupplierService();   
    }
}
//供應商B一樣須要實現一份
public class BSupplierService implements Service {   
    @Override   
    public boolean getGoods() {      
        System.out.printf("如今是 B供應商 供貨流程...\r\n");      
        return true;   
    }
}
public class BSupplierProvider implements Provider {   
    static {      
        ServiceManager.registerProvider("B", new BSupplierProvider());
    }   
    @Override   
    public Service newService() {      
        return new BSupplierService();   
    }
}
//到如今基本已經完成,接下來咱們測試下結果
@Test
public void test() throws ClassNotFoundException{
    //註冊兩家供應商
    Class.forName("package.ASupplierProvider");   
    Class.forName("package.BSupplierProvider");   
    //向A供應商要貨   
    Service serviceA = ServiceManager.newInstance("A");
    serviceA.getGoods();   
    //向B供應商要貨   
    Service serviceB = ServiceManager.newInstance("B");   
    serviceB.getGoods();
}
//console輸出:
/*  
如今是 A供應商 供貨流程...
如今是 B供應商 供貨流程...  
*/

  服務提供者框架解耦方式是這種將服務提供方和多個提供者實現之間的解耦。在上面例子中咱們動態建立了A、B兩個供應商,並調用他們的服務,這時候供應商能夠不存在,這些服務須要各個供應商本身實現,這無疑提升代碼的靈活性、可伸縮性。

1.4 構建器(Builder)

  必須得聲明的是,這裏的Builder並不是Gof中的Builder模式,這裏介紹的正如題所述,是建立一個類實例的方法。
  咱們繼續拿Shop類舉例,咱們建立一個Shop對象的時候,爲其屬性賦值經常使用方法就那麼幾個。我麼建立一個個構造函數,爲適應不一樣需求重載多個方法,這種時候你必定很苦惱。或者咱們用JavaBean的模式爲屬性賦值,這個確實也沒有什麼大的問題,但卻有可能使對象處於不一致的狀態,由於執行賦值(setter)極可能在多個方法或代碼塊之中,這時候咱們就沒法保證其對象狀態的一致性了。
  不少時候,如代碼塊(#code 1.1 part 5)所述,.Net可使用對象的初始化器來完成對象初始化,那麼Java又該如何實現相似的方法呢?遺憾的是,這須要咱們寫更多臃腫的代碼來讓咱們調用的時候更加簡潔安全。

#code 1.4 part 1
public interface Build<T> {  T build(); }
public class Shop {   
    private int shopId;   
    private String shopName;   
    private String address;   
    public int getShopId() { return shopId; }   
    public String getShopName() { return shopName; }   
    public String getAddress() { return address; }   
    public Shop(Builder builder) {      
        this.shopId = builder.shopId;      
        this.shopName = builder.shopName;      
        this.address = builder.address;   
    }   
    public static class Builder implements Build<Shop> {      
        private int shopId;      
        private String shopName;      
        private String address;      
        @Override      
        public Shop build() { return new Shop(this); }      
        public Builder shopId(Integer shopId) { 
            this.shopId = shopId; 
            return this; 
        }      
        public Builder shopName(String shopName) {
            this.shopName = shopName;         
            return this;      
        }      
        public Builder address(String address) {
            this.address = address;
            return this;      
        }
    }
}
//test
@Test
public void test() {   
    Shop shop = new Shop.Builder()         
                .shopId(23)
                .address("順天路522號")
                .shopName("點點")
                .build();   
    Assert.assertEquals(shop.getShopId(),23); 
    Assert.assertEquals(shop.getAddress(),"順天路522號"); 
    Assert.assertEquals(shop.getShopName(),"點點");
}
//console :測試經過

  咱們要知道的一點是,Builder方式會消耗更多的性能,產生更多額外的代碼,可是與此相比,Builder方式帶來的更好的編程體驗、更好的可維護性、更好的安全性是否值得咱們去使用,這是咱們平常開發中須要思考的,凡事皆有利弊,經過咱們的經驗對它作正確的取捨。

  第一部分就到此結束,但這個話題還沒結束,接下來我會繼續帶來《如何建立一個對象》剩下的內容。

相關文章
相關標籤/搜索