อ่าน Effective Java ตอนที่ 1 (Factory Method)

เนื่องจากช่วงนี้ ได้พบเจอหนังสือเล่มหนึ่งครับ Effective Java ที่เป็นหนังสือที่ดีเล่มหนึ่ง เพื่อให้การเขียน Java ของผม อีกทั้งมีหัวหน้าของผมบอกว่า “ไปอ่านซะ!” ความจริงก็อ่านหลายเรื่องมาสักพัก แล้วก็พัก ไม่ได้อ่านบ้าง เพราะมัวแต่ทำอย่างอื่นอยู่ ก็เลยคิดว่าทำยังไงดีนะที่ทำให้ได้อ่านให้จบให้ได้ แล้วก็เลยคิดว่า งั้นเขียน blog ถึงมันเลยแล้วกัน (ผมจะไม่เขียนภาษาอังกฤษนะ ถ้าจะอ่านภาษาอังกฤษ ก็ไปอ่าน pdf เลยดีกว่านะ ฮ่าๆ)

โดยในที่นี้ผมจะข้าม Basic ต่างๆไปเลยแล้วกัน เพราะจะได้ไม่ยืดเยื้อ (บทความนี้น่าจะเหมาะกับคนที่มีพื้นฐานด้าน Programming, OOP หรือ Java มาก่อน) เรามาเริ่มเลยดีกว่า จะได้ไม่ยืดเยื้อครับ

เรื่องที่ 1 การเลือกใช้ Static Factory Method แทนที่จะให้ Constructor

แน่นอนครับ การสร้าง Object ต่างๆก็เกิดจากการเรียก Constructor แต่ว่าการใช้ Constructor ก็ไม่ได้ดีที่สุดเสมอไป Static Factory Method ก็เป็นอย่างหนึ่งที่ดีกว่าในบางโอกาส

ข้อดีอย่างแรกของ Static Factory Method คือ มันมีชื่อ แต่ในทางตรงกันข้าม มัน Constructor นั้นไม่มี (ใช้ชื่อเดียวกับ class) การมีชื่อนั้นทำให้เราสามารถอ่าน code และใช้ได้ง่ายขึ้น สามารถเดาได้ว่า result ที่ต้องการจะออกมาเป็น Object แบบไหน ยกตัวอย่างก็อย่างเช่น

BigInteger bi = new BigInteger(bitLength, certainly, random)

เราไม่สามารถบอกได้ว่ามันมีไว้ทำไม หรือสร้างอะไร แต่จริงๆแล้วมันเอาไว้ใช้สร้าง BigInteger ที่อาจจะเป็น ช่องว่างจำนวนเฉพาะ(probablePrime) แต่ถ้าเราใช้  Static Factory Method ที่เขียนเป็น

BigInteger bi = BigInteger.probablePrime(range, random)

เราก็สามารถเดาได้ทันทีว่าอ่อ Method นี้ใช้สร้างอะไร

ข้อดีอย่างที่สองคือ มันไม่จำเป็นต้องสร้าง Object ใหม่เวลามันโดนเรียก ซึ่งข้อนี้ต่างจาก Constructor อย่างสิ้นเชิง เพราะ Constructor นั้นจำเป็นต้องสร้าง ซึ่งอาจจะเป็นการสร้าง Object ที่ซ้ำกันหรือไม่จำเป็นขึ้นมา ก็ยกตัวอย่างเช่น

Boolean.valueOf(b)

ที่มันจะไม่ได้สร้าง Object ใหม่ แต่จะ return ตัว Boolean Object ที่มีค่า true หรือ false ออกมา (ไม่ใช่ boolean นะ แต่เป็น Boolean) โดยเทคนิคนี้ก็เหมือนกับ Flyweight pattern ที่คงจะได้อ่านในอนาคต ซึ่งมันเอาไว้ใช้กับพวก Instance-controlled ต่างๆ เช่นพวก Singleton หรือพวก noninstantiable ซึ่งการนี้มันจะช่วยให้เราสามารถใช้ == ได้เลยด้วย (ก็มัน object เดียวกันชัดๆ) แต่ก็ไม่ใช้ทั้งหมดที่จะใช้ตัวเก่าได้ในบางกรณี อย่างของ Java Platform เองก็ยังมีบางส่วนที่ไม่ได้ใช้ == ได้ เพราะไม่ได้ใช้ Object เดิม ในกรณีที่เป็นการ คืน Object เดิมเท่านั้นที่เอามา == ได้นะ

ข้อดีอย่างที่สามคือ มันสามารถใช้สร้าง Object ที่เป็น subtype ของ type ของตัวมันได้ ซึ่งมันช่วยให้เราสามารถนำมาใช้ได้อย่างคล่องตัวมากขึ้น

ยกตัวอย่างก็เช่นการสร้าง API ที่สามารถส่งคืน object ที่ตัวมันอาจจะไม่ public ทำให้เราสามารถซ่อน implementation class ต่างๆซึ่งมันทำให้ API เราเล็กมากๆ (แถมใช้ง่าย) โดยการทำแบบนี้มันนำไปพัฒนาต่อเป็น interface-based frameworks (ซึ่งจะได้อ่านในอนาคต) ให้ยกตัวอย่างก็คงเป็น

// Service provider framework sketch 
// Service interface 
public interface Service {
  ... // Service-specific methods go here 
} 

// Service provider interface 
public interface Provider { 
  Service newService();
}

// Noninstantiable class for service registration and access 
public class Services { 

  private Services() { } // Prevents instantiation

  // Maps service names to services 
  private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
  public static final String DEFAULT_PROVIDER_NAME = "";

  // Provider registration API 
  public static void registerDefaultProvider(Provider p) { 
    registerProvider(DEFAULT_PROVIDER_NAME, p);
  } 
  public static void registerProvider(String name, Provider p){ 
    providers.put(name, p); 
  } 

  // Service access API 
  public static Service newInstance() { 
    return newInstance(DEFAULT_PROVIDER_NAME); 
  } 
  public static Service newInstance(String name) { 
    Provider p = providers.get(name); 
    if (p == null) 
      throw new IllegalArgumentException( "No provider registered with name: " + name); 
    return p.newService(); 
  } 
}

ข้อดีข้อที่สี่คือ Static Factory Method มันสามารถลดความซับซ้อนต่างในการสร้าง instance ที่มีการใช้ parameter ถ้าให้พูดคือมันทำให้เราเขียน code อ่าน code ได้ง่ายขึ้นมาก แถมลดความซับซ้อนและซ้ำซ้อนได้ เช่นถ้าเราเขียนแบบนี้

Map<String, List<String>> m = new HashMap<String, List<String>>();

คงเหนื่อย แถมอ่านยากอีกต่างหาก แต่ถ้าใช้ Static Factory Method เนื่องจาก Compiler สามารถบอกได้ว่า type ของ parameter คืออะไร (เรียกกันว่า type inference) ยกตัวอย่างก็อย่างเช่น Static Factory Method ของ HashMap ที่เขาเขียนมาแล้ว

public static <K, V> HashMap<K, V> newInstance() { 
    return new HashMap<K, V>(); 
}

ซึ่งเวลาใช้ก็แค่เขียน

Map<String, List<String>> m = HashMap.newInstance();

(ไอ้ตัวนี้ใช้ได้ตั้งแต่ java 1.6 ขึ้่นไปนะ ถ้าก่อนหน้านั้นคงต้องทำ Utility class เอาหน่ะ)

ส่วนเรื่อง ข้อเสีย ก็ไม่ใช่ว่าไม่มีนะ

ข้อเสียหลักๆข้อแรกคือ Static Factory Method มันคือ class ที่ไม่มี public หรือ protected constructor ซึ่งทำให้มันไม่สามารถสร้าง subclass ได้ แล้วก็เช่นกันกับ class ที่ไม่ได้เป็น public ที่ return จาก Factory Method นั้นๆด้วย ซึ่งทำให้ไม่สามารถ subclass พวกนั้นได้ เพื่อที่เราจะได้ใช้ความสามารถต่างๆของพวกมัน ซึ่งถ้าจะใช้จริงๆก็คงต้องไปใช้การ Composition แทน

สรุปว่า Static Factory Method และ Public Constructor ทั้งสองอย่างนั้นมันมีการใช้ของมันเอง เราก็ต้องมานั่งเลือกกันครับ ว่าตัวไหนดีกับเหตุการณ์ไหน บางที Public Constructor อาจจะดีกว่าการเขียน Factory Method ก็ได้ครับ แต่ไม่ใช่ว่า Public Constructor ก็อย่าลืมคิดถึง Static Factory Method ด้วยนะ

Comments