บทนำ
วิธีการดั้งเดิมในการรับอินสแตนซ์ของคลาสคือคอนสตรัคเตอร์แบบ public
```javascript public class Member {
}
public enum MemberStatus {
}
แม้ว่าโดยทั่วไปแล้วคอนสตรัคเตอร์แบบ public จะเพียงพอแล้ว แต่บ่อยครั้งที่การมีเมธอดแบบคงที่ (static factory method) นอกเหนือจากคอนสตรัคเตอร์สามารถทำให้ผู้ใช้สร้างอินสแตนซ์ได้ง่ายขึ้นตามที่ตั้งใจไว้
ตัวอย่างทั่วไปของวิธีการสร้างแบบคงที่คือวิธี valueOf() ของ Boolean
```javascript public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
เมธอดด้านบนรับค่า boolean เป็นค่าอินพุตและส่งคืนเป็นวัตถุ Boolean
ข้อดีของวิธีการสร้างแบบคงที่
สามารถมีชื่อได้
ตามพารามิเตอร์และคอนสตรัคเตอร์เองไม่สามารถอธิบายลักษณะของวัตถุที่จะส่งคืนได้อย่างเพียงพอ ตัวอย่างเช่น จากคอนสตรัคเตอร์หลัก (name, age, hobby, memberStatus) ของคลาส Member ข้างต้น คุณไม่สามารถบอกได้ว่าสมาชิกมีลักษณะอย่างไร
นอกจากนี้ สำหรับลายเซ็นเดียว คุณสามารถสร้างคอนสตรัคเตอร์ได้เพียงหนึ่งแบบเท่านั้น ในขณะที่เมธอดแบบคงที่สามารถมีชื่อได้ ดังนั้นคุณจึงสามารถสร้างเมธอดแบบคงที่หลายรายการสำหรับลายเซ็นเดียวเพื่อส่งคืนอินสแตนซ์
```javascript public class Member {
}
การสร้างเมธอดแบบคงที่หลายรายการสำหรับลายเซ็นเดียวกันที่มีการแยกความแตกต่างโดย MemberStatus นั้นดีกว่าการสร้างคอนสตรัคเตอร์หลักเพียงรายการเดียว ในกรณีนี้ ผู้ใช้สามารถสร้างอินสแตนซ์สมาชิกที่มีทักษะเฉพาะได้โดยไม่ต้องสับสน
พิจารณาไลบรารีที่กำหนดไว้ใน JDK มีวิธีการแบบคงที่ probablePrime() ของ BigInteger
```javascript public static BigInteger probablePrime(int bitLength, Random rnd) { if (bitLength < 2) throw new ArithmeticException("bitLength < 2");
}
หากเปรียบเทียบเมธอดแบบคงที่ probablePrime() กับคอนสตรัคเตอร์ทั่วไปของ BigInteger ค่าหลังจะมีประโยชน์มากกว่ามาก เมื่อคุณกำลังมองหาวัตถุ BigIntegerที่มีค่าเป็นจำนวนเฉพาะ
ไม่จำเป็นต้องสร้างอินสแตนซ์ใหม่ทุกครั้งที่มีการเรียกใช้
```javascript public static Boolean valueOf(boolean b) { return (b ? Boolean.TRUE : Boolean.FALSE); }
เมธอด valueOf() ของ Boolean แสดงให้เห็นว่าอินสแตนซ์ถูกแคชไว้ล่วงหน้าและส่งคืน พฤติกรรมนี้อาจเพิ่มประสิทธิภาพได้อย่างมาก ในสถานการณ์ที่ต้นทุนการสร้างสูงและวัตถุมีการร้องขอบ่อย flyweight patternคล้ายกับเทคนิคนี้
คลาสที่ใช้รูปแบบเมธอดแบบคงที่เพื่อส่งคืนอินสแตนซ์เดียวกันสำหรับการร้องขอที่ซ้ำกัน เรียกว่าคลาสควบคุมอินสแตนซ์ ด้วยการควบคุมอินสแตนซ์ คุณสามารถสร้างคลาสซิงเกิลตันหรือคลาสที่ไม่สามารถแปลงเป็นอินสแตนซ์ได้ นอกจากนี้ คุณยังสามารถรับประกันได้ว่ามีอินสแตนซ์คงที่เดียวในคลาสค่าคงที่
การควบคุมอินสแตนซ์เป็นพื้นฐานสำคัญของรูปแบบ flyweight และชนิดการแจกแจงรับประกันว่าอินสแตนซ์มีอยู่เพียงตัวเดียว
ตัวอย่าง
ใน Minecraft คุณต้องปลูกต้นไม้ หากคุณสร้างวัตถุทรีใหม่ทุกครั้ง จะนำไปสู่หน่วยความจำล้น
ดังนั้น คุณควรบันทึกต้นไม้ชนิดต่างๆ (ต้นไม้สีแดงและต้นไม้สีเขียว) ไว้ แล้วเปลี่ยนแปลงเฉพาะตำแหน่งเท่านั้น แน่นอน สีอาจมีมากกว่าสองสี ดังนั้น หากคุณเก็บต้นไม้ไว้ในโครงสร้างข้อมูล เช่นMap คุณจะทำได้อย่างมีประสิทธิภาพ
```javascript public class Tree {
}
public class TreeFactory { // ใช้ออบเจ็กต์แฮชแมปเพื่อจัดการต้นไม้ที่สร้างขึ้น public static final Map<String, Tree> treeMap = new HashMap<>();
}
public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in);
}
ความแตกต่างจากรูปแบบซิงเกิลตัน
รูปแบบซิงเกิลตันอนุญาตให้คุณสร้างต้นไม้ได้เพียงต้นเดียวในคลาส Tree ดังนั้น หากคุณใช้รูปแบบซิงเกิลตัน คุณต้องเปลี่ยนสีของวัตถุต้นไม้ที่สร้างขึ้น กล่าวคือ รูปแบบซิงเกิลตัน สามารถมีได้เพียงตัวเดียวโดยไม่คำนึงถึงประเภท
กรณีการใช้งาน
รูปแบบ flyweight ใช้ในพูลคงที่ของสตริงของ Java
สามารถส่งคืนวัตถุประเภทย่อยของประเภทส่งคืนได้
หากคุณเคยใช้เมธอด asList() ของคลาสอรรถประโยชน์ Arrays คุณจะเข้าใจข้อดีนี้
```javascript
public static
ส่งคืนค่าที่ห่อหุ้มไว้เป็น ArrayList ซึ่งเป็นคลาสการนำไปใช้จริงของ List ผู้ใช้ไม่จำเป็นต้องรู้แม้กระทั่งการนำไปใช้จริง ผู้ใช้มีอิสระในการเลือกคลาสส่งคืน นั่นคือ ความยืดหยุ่นที่ได้จากการเลือกคลาสส่งคืนโดยอิสระ ช่วยให้ผู้พัฒนาสามารถทำให้ API มีขนาดเล็กลงโดยไม่เปิดเผยการใช้งาน
เกี่ยวกับเรื่องของเมธอดคงที่ของอินเทอร์เฟซ Java
ก่อน Java 8 ไม่มีวิธีการประกาศเมธอดคงที่ในอินเทอร์เฟซ ดังนั้น หากคุณต้องการเมธอดคงที่ที่จะส่งคืนอินเทอร์เฟซที่มีชื่อ“Type” คุณต้องสร้างคลาสคู่หูที่ไม่สามารถสร้างอินสแตนซ์ได้ซึ่งมีวิธีการภายใน
ตัวอย่างทั่วไปคือ 45 คลาสการใช้งานยูทิลิตี้ที่ JCF จัดไว้ โดยคลาสการใช้งานเหล่านี้ส่วนใหญ่ได้รับการประกาศว่าเป็นเมธอดคงที่จากคลาสคู่หูเพียงตัวเดียว java.util.Collectionsโดยเฉพาะ อย่างยิ่ง ในคลาสการใช้งานเหล่านี้ มีบางคลาสที่ไม่ใช่แบบสาธารณะ กล่าวคือ มีเพียงเมธอดคงที่เท่านั้นที่สามารถสร้างอินสแตนซ์ได้ (คลาสการใช้งานนี้ไม่สามารถสืบทอดได้อย่างชัดเจน)
นอกจากนี้ เนื่องจากไม่ได้เผยแพร่คลาสการใช้งาน 45 คลาส API จึงมีขนาดเล็กลงมากมาย
```javascript
// ตัวอย่างของอินเทอร์เฟซและคลาสคู่หู
List
อย่างไรก็ตาม ตั้งแต่ Java 8 อินเทอร์เฟซมีเมธอดคงที่โดยตรง ดังนั้น คุณจึงไม่จำเป็นต้องกำหนดคลาสคู่หูแยกต่างหาก
สามารถส่งคืนประเภทคลาссыย่อยต่างกันตามพารามิเตอร์อินพุต
ยิ่งไปกว่านั้น นอกเหนือจากการส่งคืนประเภทย่อย การเปรียบเทียบกับค่าพารามิเตอร์สามารถใช้เพื่อส่งคืนประเภทย่อยอื่นได้ ตัวอย่างเช่น หากคุณต้องการส่งคืน MemberStatus ต่างกันตามคะแนน คุณสามารถสร้างเมธอดแบบคงที่ด้านล่างและสร้างตรรกะการเปรียบเทียบภายใน
```javascript public enum MemberStatus {
}
@DisplayName("การทดสอบ MemberStatus") class MemberStatusTest {
}
ขณะสร้างเมธอดแบบคงที่ ไม่จำเป็นต้องมีคลาสของวัตถุที่จะส่งคืน
ในประโยคข้างต้น คลาสของวัตถุคือไฟล์คลาสที่เราสร้าง
โปรดทราบว่า Class<?> หมายถึงวัตถุ Class ที่โหลดเดอร์คลาสจัดสรรให้กับฮีปเมื่อโหลดคลาส วัตถุ Class นี้มีเมตาข้อมูลต่างๆ ของคลาสที่เราเขียน
```javascript package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
}
หากคุณลองใช้โค้ดด้านบน จะเห็นได้ว่าอินเทอร์เฟซการใช้งานถูกสร้างขึ้นโดยใช้การสะท้อน และอินสแตนซ์การใช้งานจริงจะถูกเตรียมโดยใช้เทคนิคการสะท้อน ณ จุดนี้ เวลาสร้างเมธอดแบบคงที่ไม่จำเป็นต้องมีคลาส StaticFactoryMethodTypeChild
ถ้าไม่มีคลาสการใช้งานในพาธ algorithm.dataStructure.StaticFactoryMethodTypeChild ในเวลาที่ใช้เมธอดแบบคงที่ ข้อผิดพลาดจะเกิดขึ้น แต่ไม่มีปัญหา ในขณะที่สร้างเมธอดแบบคงที่ นั่นคือ เหตุผลที่เราบอกว่ามีความยืดหยุ่น
```javascript public interface Test {
}
public class Main {
}
แม้ว่าคุณจะไม่ใช้การสะท้อน แต่คุณก็สามารถรับความยืดหยุ่นแบบเดียวกันได้ หากคุณลองดูที่ create() เมธอดแบบคงที่ของ Test จะเห็นว่าไม่มี คลาสการใช้งาน แต่ไม่มีปัญหาในขณะที่เขียน แน่นอน NPE จะเกิดขึ้นในเวลาจริง ดังนั้น คุณต้องส่งคืนคลาสการใช้งานในภายหลัง
ความยืดหยุ่นนี้เป็นรากฐานสำคัญของการสร้างเฟรมเวิร์กผู้ให้บริการ และตัวอย่างทั่วไปคือ JDBC เฟรมเวิร์กผู้ให้บริการ JDBC เป็นผู้ให้บริการและไคลเอ็นต์มีหน้าที่รับผิดชอบในการควบคุมการเข้าถึงบริการ (Dependency Inversion Principle)
- องค์ประกอบของเฟรมเวิร์กผู้ให้บริการ
- อินเทอร์เฟซเซอร์วิส
- กำหนดพฤติกรรมของคลาสการใช้งาน
- JDBC Connection
- API การลงทะเบียนผู้ให้บริการ
- ผู้ให้บริการลงทะเบียนคลาสการใช้งาน
- JDBC DriverManager.registerDriver()
- API การเข้าถึงบริการ
- ไคลเอ็นต์ใช้เมื่อต้องการได้รับอินสแตนซ์ของบริการ และคลาสการใช้งานเริ่มต้น หรือคลาสการใช้งานที่รองรับจะถูกส่งคืนแบบวนซ้ำหากไม่มีระบุเงื่อนไข
- สอดคล้องกับเมธอดแบบคงที่
- JDBC DriverManager.getConnection()
- (ตัวเลือก) อินเทอร์เฟซผู้ให้บริการ
- หากไม่มีอินเทอร์เฟซนี้ คุณต้องใช้การสะท้อนเพื่อสร้างอินสแตนซ์ของแต่ละคลาสการใช้งาน
- JDBC Driver
- อินเทอร์เฟซเซอร์วิส
เฟรมเวิร์กผู้ให้บริการมีตัวแปรหลายตัว และมีรูปแบบบริดจ์ และเฟรมเวิร์กการพึ่งพา
ตัวอย่าง JDBC ทั่วไป
```javascript Class.forName("oracle.jdbc.driver.OracleDriver"); Connection connection = null; connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
// ตรรกะ sql โดยใช้ Statement ต่างๆ
โดยทั่วไปแล้ว JDBC ถูกเขียนดังนี้ โดยลงทะเบียน OracleDriver ซึ่งเป็นหนึ่งในคลาสการใช้งานของ Driver ผ่าน Class.forName() และรับ อินสแตนซ์ OracleDriver เป็นหนึ่งในคลาสการใช้งานของ Connection ผ่าน DriverManager.getConnection()
ที่นี่ Connection คืออินเทอร์เฟซเซอร์วิส DriverManager.getConnection() คือ API การเข้าถึงเซอร์วิส และ Driver คืออินเทอร์เฟซผู้ให้บริการ อย่างไรก็ตาม API การลงทะเบียนผู้ให้บริการ DriverManager.registerDriver() ไม่ได้ถูกใช้ แม้จะไม่เป็นเช่นนั้น แต่เราสามารถลงทะเบียน OracleDriver ซึ่งเป็นคลาสการใช้งานของ Driverใช้ Class.forName() เท่านั้น เป็นไปได้อย่างไร
กลไกของ Class.forName()
เมธอดนี้จะขอให้ JVM โหลดคลาสนี้โดยการป้อนชื่อไฟล์คลาสทางกายภาพเป็นอาร์กิวเมนต์ จากนั้น ตัวโหลดคลาสจะจัดสรร Class วัตถุในพื้นที่วิธีการในขณะที่บันทึกเมตาข้อมูลของคลาส นอกจากนี้ ในตอนท้ายของการโหลดคลาส ฟิลด์คงที่และบล็อกคงที่จะถูกเตรียมไว้ และที่นี่ API การลงทะเบียนผู้ให้บริการจะถูกเรียกใช้
```javascript public class OracleDriver implements Driver {
}
ที่จริงแล้ว หากคุณดู OracleDriver คุณจะเห็นว่ามีการลงทะเบียน OracleDriver ซึ่งเป็นคลาสการใช้งานของ Driver โดยใช้ DriverManager.registerDriver() ใน บล็อกคงที่ ดังนั้น ลูกค้าสามารถรับอินสแตนซ์ของคลาสการใช้งานของ Connection ได้โดยใช้ getConnection()
หากคุณดู API การเข้าถึงแบบผู้ใช้ getConnection() อย่างใกล้ชิด คุณจะเห็นว่าใช้วิธีการรับ Connection จากอินเทอร์เฟซ Driver หากไม่มี อินเทอร์เฟซเซอร์วิส Driver คุณสามารถใช้การสะท้อน เช่น Class.forName() เพื่อส่งคืนคลาสการใช้งาน Connection ที่คุณต้องการ ณ จุดนี้ ไม่จำเป็นต้องมีคลาสการใช้งาน Connection ในเวลาที่สร้างเมธอดแบบคงที่
แทนที่จะเป็น เราใช้ Driver interface และสามารถรับอินสแตนซ์ของคลาสการใช้งาน Driver แบบไดนามิก และรับอินสแตนซ์ของคลาสการใช้งาน Connection ที่เหมาะสมได้อย่างง่ายดาย
โปรดสังเกตว่า หากคุณวิเคราะห์โค้ด JDK จริงของเมธอด getConnection() ของ DriverManager คุณอาจข้ามไปได้หากไม่สนใจมาก
```javascript @CallerSensitive public static Connection getConnection(String url, String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties();
}
เมธอด getConnection() สาธารณะคงที่ ถูกเรียกใช้ก่อน จากนั้น url, Properties และ CallerClass จะถูกส่งไปยังเมธอด getConnection() ส่วนตัวคงที่ ในขณะเดียวกัน Reflection.getCallerClass() มีบทบาทในการรับคลาสที่เรียกใช้เมธอด getConnection() สาธารณะคงที่นี้ ตัวอย่างเช่น หากคลาส Car เรียก getConnection() ตัวโหลดคลาสจะรับคลาสวัตถุโดย Reflection.getCallerClass()
```javascript private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } }
}
callerCL เป็นวัตถุตัวโหลดคลาส และสร้างขึ้นโดยตัวโหลดคลาสของ caller หรือเธรดปัจจุบัน จากนั้น รายการไดรเวอร์ที่ลงทะเบียนจากแอปพลิเคชันปัจจุบันaDriver จะถูกรวมทีละรายการ ณ จุดนี้ isDriverAllowed() ตรวจสอบว่า aDriver มีอยู่ในการโทรหรือไม่
ข้อดีของเฟรมเวิร์ก JDBC
ประเด็นสำคัญของเฟรมเวิร์ก JDBC คือ อินเทอร์เฟซ Driver อินเทอร์เฟซ Connection และคลาสการใช้งาน ที่ใช้ในการนำอินเทอร์เฟซเหล่านี้ไปใช้นั้นแยกออกจากกันอย่างสมบูรณ์ สามารถสร้างอินเทอร์เฟซโดยใช้เทมเพลต และหากคุณสร้างคลาสการใช้งานตามเทมเพลต นั้น จะมีความยืดหยุ่นสูงมาก
ด้วยเหตุนี้ แม้ว่าจะมี DBMS ตัวอื่นปรากฏขึ้น แต่ผู้ขายสามารถจัดเตรียมไดรเวอร์และอินเทอร์เฟซ Connection เพื่อให้นักพัฒนาที่ใช้ Java สามารถใช้ API ไดรเวอร์ DBMS อื่นในลักษณะเดียวกับ JDBC
ข้อเสียของวิธีการสร้างแบบคงที่
ไม่สามารถสร้างคลาสย่อยได้ เนื่องจากต้องมีคอนสตรัคเตอร์ public หรือ protected เพื่อการสืบทอด
อย่างไรก็ตาม ข้อจำกัดนี้อาจเป็นข้อดีได้ เนื่องจากสนับสนุนการเขียนคลาสแทนการสืบทอด และจำเป็นต้องปฏิบัติตามข้อจำกัดนี้เพื่อสร้างคลาสคงที่
เป็นการยากสำหรับโปรแกรมเมอร์ในการค้นหาวิธีการแบบคงที่
เนื่องจากวิธีการแบบคงที่ไม่ได้อธิบายไว้อย่างชัดเจนในเอกสาร API นักพัฒนาจึงควรเขียนเอกสาร API อย่างดีและปฏิบัติตามอนุสัญญาการตั้งชื่อที่รู้จักกันทั่วไปเพื่อบรรเทาปัญหา
วิธีการตั้งชื่อเมธอดแบบคงที่
- จาก
- รับพารามิเตอร์เดียวและส่งคืนอินสแตนซ์ของประเภทนั้น
- Date date = Date.from(instant);
- ของ
- รับหลายพารามิเตอร์และส่งคืนอินสแตนซ์ของประเภทที่เหมาะสม
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- เวอร์ชันที่ละเอียดมากขึ้นของ from และ of
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance หรือ getInstance
- ส่งคืนอินสแตนซ์ที่ระบุโดยพารามิเตอร์ แต่ไม่รับประกันว่าจะเป็นอินสแตนซ์เดียวกัน
- StackWalker luke = StackWalker.getInstance(options);
- create หรือ newInstance
- เหมือนกับ instance หรือ getInstance แต่รับประกันว่าจะส่งคืนอินสแตนซ์ใหม่เสมอ
- Object newArray = Array.newInstance(classObject, arraylen);
- type
- เหมือนกับ getType และ newType แต่สั้นกว่า
- List<Complaint> litany = Collections.list(legacyLitany);
บทสรุป
ใช้วิธีการแบบคงที่และคอนสตรัคเตอร์แบบ public อย่างเหมาะสม
ความคิดเห็น0