这是一个开篇,不管博客仍是论坛,都一直潜水,享受各位做者带来的分享,却一直没有什么回馈。
从我建立博客几年来,只有渺渺两篇,仍是当时初学编程时的总结,由于总总缘由,这个也未坚持下去。几年来一直想要动笔,却一直找不到什么契机,大概是怯于表达,或以为积累不够,或怠于坚持,我想归根结底仍是太懒。
java
最近一段时间,忽然发现建立对象颇有意思,这个虽然简单,但每每容易被人忽视,起码在此以前,我就从未以这种角度去总结过它。
在此以前咱们要建立一个简单的商店类(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"; } }
这能够说是最简单最多见的建立对象实例的方式,不少时候,咱们都会不由自主的这么写: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号"; };
固然,上述代码均可以经过反射代替,下面是无参构造的反射实例: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(); }
静态工厂方法,顾名思义,必定是静态的,但却不一样于设计模式的工厂,这里仅仅是返回一个类实例的静态方法。既然是静态方法,本质上和该类当中的其它静态方法并没有任何区别。
咱们先经过一段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两个供应商,并调用他们的服务,这时候供应商能够不存在,这些服务须要各个供应商本身实现,这无疑提升代码的灵活性、可伸缩性。
必须得声明的是,这里的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方式带来的更好的编程体验、更好的可维护性、更好的安全性是否值得咱们去使用,这是咱们平常开发中须要思考的,凡事皆有利弊,经过咱们的经验对它作正确的取舍。
第一部分就到此结束,但这个话题还没结束,接下来我会继续带来《如何建立一个对象》剩下的内容。