![translation](https://cdn.durumis.com/common/trans.png)
Bu, AI tarafından çevrilen bir gönderidir.
Dil Seç
Text summarized by durumis AI
- Statik fabrika yöntemleri, yapıcılardan daha fazla avantaja sahiptir ve sınıf örneklerinin oluşturulmasını etkili bir şekilde kontrol edebilir.
- JDBC, statik fabrika yöntemlerinin kullanıldığı temsili bir örnek olup, hizmet sağlayıcı çerçeve kalıbı aracılığıyla esnek ve genişletilebilir bir mimari sağlar.
- Statik fabrika yöntemleri, mirasın kısıtlamaları ve geliştiriciler için zor bulunması gibi dezavantajları vardır, ancak bunları iyi şekilde kullanılırsa iyi bir tasarım elde edilebilir.
public class Member {
private String name;
private int age;
private String hobby;
private MemberStatus memberStatus;
public Member(String name, int age, String hobby, MemberStatus memberStatus) {
this.name = name;
this.age = age;
this.hobby = hobby;
this.memberStatus = memberStatus;
}
}
public enum MemberStatus {
ADVANCED,
INTERMEDIATE,
BASIC;
Genel olarak, genel oluşturucular yeterlidir, ancak oluşturuculara ek olarak statik fabrika yöntemleri (static factory methods) sağlamak, kullanıcıların istedikleri gibi kolayca nesneler oluşturmalarını sağlar.
Statik fabrika yöntemlerinin tipik bir örneği, Boolean sınıfının valueOf() yöntemidir.
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
Yukarıdaki yöntem, temel veri türü olan boolean değerini alır ve bir Boolean nesnesi oluşturur ve döndürür.
Statik fabrika yönteminin avantajları
İsim verilebilir.
Oluşturucuya iletilen parametreler ve oluşturucunun kendisi, döndürülecek nesnenin doğasını tam olarak açıklayamaz. Yukarıdaki Member sınıfının ana oluşturucusu (ad, yaş, hobi, üyelik durumu) göz önüne alındığında, hangi özelliklere sahip bir Üye olduğunu belirlemek zordur.
Ayrıca, tek bir imza ile yalnızca bir oluşturucu oluşturabilirsiniz, statik fabrika yöntemi adlandırılabileceğinden, tek bir imza ile döndürülecek çok sayıda statik fabrika yöntemi oluşturabilirsiniz.
public class Member {
private String name;
private int age;
private String hobby;
private MemberStatus memberStatus;
public Member(String name, int age, String hobby, MemberStatus memberStatus) {
this.name = name;
this.age = age;
this.hobby = hobby;
this.memberStatus = memberStatus;
}
public static Member basicMember(String name, int age, String hobby) {
return new Member(name, age, hobby, MemberStatus.BASIC);
}
public static Member intermediateMember(String name, int age, String hobby) {
return new Member(name, age, hobby, MemberStatus.INTERMEDIATE);
}
public static Member advancedMember(String name, int age, String hobby) {
return new Member(name, age, hobby, MemberStatus.ADVANCED);
}
Yukarıdaki gibi, Üyeyi oluşturucunun kendisiyle MemberStatus'a göre ayırmak yerine, aynı imzaya sahip birden fazla statik fabrika yöntemi oluşturursanız, kullanıcılar karışıklık yaratmadan belirli bir yeteneğe sahip Üye örnekleri oluşturabilirler.
JDK'da tanımlanan kütüphanelere bakalım, BigInteger'nin statik fabrika yöntemi olan probablePrime().
public static BigInteger probablePrime(int bitLength, Random rnd) {
if (bitLength < 2)
throw new ArithmeticException("bitLength < 2");
return (bitLength < SMALL_PRIME_THRESHOLD ?
smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
BigInteger'nin genel oluşturucusu ve statik fabrika yöntemi인 probablePrime() ile karşılaştırıldığında, değerinin bir asal sayı olduğunu söyleyen ikincisinin daha açıklayıcı olduğu söylenebilir.
Her çağrıda yeni bir örnek oluşturması gerekmez.
public static Boolean valueOf(boolean b) {
return (b ? Boolean.TRUE : Boolean.FALSE);
Boolean'ın valueOf() yöntemi, örnekleri önceden önbelleğe alıp döndürdüğü söylenebilir. Bu özellik, oluşturma maliyeti yüksek nesnelerin sık sık istendiği durumlarda performansı önemli ölçüde artırabilir ve Flyweight deseniaynı tekniğe benzetilebilir.
Aynı nesneyi tekrarlayan istekler için döndüren statik fabrika yöntemi kullanan sınıflar, örnek ömrünü yönetebilir ve bu nedenle örnek denetleyici sınıflar olarak adlandırılırlar. Örnekleri denetleyerek, tekli sınıflar oluşturabilir veya örnek oluşturulamayan sınıflar oluşturabilirsiniz. Ayrıca, değişmeyen değer sınıflarında tek bir örnek olmasını sağlayabilirsiniz.
Örnek denetimi, Hafif Ağırlık deseninin omurgasını oluşturur ve enum türleri yalnızca bir örnek oluşturulduğundan emin olur.
Örnek
Minecraft'ta ağaç dikmeniz gerekiyor. Her ağaç için yeni bir ağaç oluşturursanız, bellek taşması yaşayabilirsiniz.
Bu nedenle, kırmızı ağaçlar ve yeşil ağaçlar gibi nesneler saklanabilir ve yalnızca konumları değiştirilerek döndürülebilir. Elbette, renk yalnızca iki renkle sınırlı değildir, daha fazla renk eklenebilir ve ağaçlar, Map gibi bir veri yapısında renklere göre saklanabilir, bu da verimli olacaktır.
public class Tree {
// Ağaç aşağıdaki 3 bilgiye sahiptir.
private String color;
private int x;
private int y;
// Yalnızca renk ile bir oluşturucu oluşturun.
public Tree(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
// Ağaç dikerken
public void install(){
System.out.println("x:"+x+" y:"+y+" konumuna "+color+" renkli ağaç eklendi!");
}
}
public class TreeFactory {
// HashMap veri yapısı kullanılarak, oluşturulan ağaçlar yönetilir.
public static final Map treeMap = new HashMap<>();
public static Tree getTree(String treeColor){
// Map'te girdi olarak alınan renkte bir ağaç olup olmadığı kontrol edilir. Varsa, bu nesne sağlanır.
Tree tree = (Tree)treeMap.get(treeColor);
// Map'te henüz aynı renkte bir ağaç yoksa, yeni bir nesne oluşturulur ve sağlanır.
if(tree == null){
tree = new Tree(treeColor);
treeMap.put(treeColor, tree);
System.out.println("Yeni nesne oluşturuldu");
}
return tree;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("İstediğiniz rengi girin :)");
for(int i=0;i<10;i++){
// Ağaç rengi girin
String input = scanner.nextLine();
// Fabrikadan bir ağaç alın
Tree tree = (Tree)TreeFactory.getTree(input);
// Ağaç x,y'yi ayarlayın ve
tree.setX((int) (Math.random()*100));
tree.setY((int) (Math.random()*100));
// Ağaç ekin
tree.install();
}
}
Singleton deseniyle farkı
Singleton deseni, Ağaç sınıfında yalnızca bir ağaç oluşturulabileceği anlamına gelir. Bu nedenle, Singleton desenini kullanırsanız, oluşturulan tek nesnenin rengi değiştirilmelidir. Yani, Singleton deseni türden bağımsız olarak yalnızca bir tane tutabilir.
Kullanım durumları
Java'nın String Constant Pool'unda Hafif Ağırlık deseni kullanılır.
Alt tiplerin nesnelerini döndürebilir.
Arrays yardımcı sınıfının asList() yöntemini kullandıysanız, bu avantajı anlayabilirsiniz.
public static List asList(T... a) [
return new ArrayList<>(a);
Değerleri List'in alt sınıfı olan ArrayList olarak döndürürken, kullanıcı bu uygulama sınıfına kadar bilmek zorunda değildir. Yani, döndürülen nesnenin sınıfı serbestçe seçilebilir; bu esneklik, geliştiricilerin uygulama sınıfını ifşa etmeden uygulamayı geri döndürebilmelerini sağlar, böylece API'nin küçük kalmasını sağlarlar.
Java arayüzlerinin statik yöntemleriyle ilgili hikaye
Java 8'den önce, arayüzlerde statik yöntemler ilan etmek mümkün değildi, bu nedenle "Tip" adı verilen, statik yöntemleri döndüren bir arayüze ihtiyaç duyuluyordu, bunun için "Tipler" adlı örnek oluşturulamayan bir yoldaş sınıfı oluşturulurdu.
Tipik bir örnek olarak, JCF'nin sağladığı 45 yardımcı uygulama sınıfı vardır ve bu uygulama sınıflarının çoğu, yalnızca adı geçen tek bir bağlantı sınıfı olan java.util.Collections aracılığıyla statik fabrika yöntemleri aracılığıyla elde edilir. Özellikle, bu uygulama sınıfları arasında genel olmadığı için yalnızca statik fabrika yöntemi aracılığıyla örnek oluşturulabilen bazıları vardır. (Bu uygulama sınıfları açıkça miras alınamaz.)
Ayrıca, 45 uygulama sınıfı da kamuya açık olmadığı için API'yi çok daha küçük hale getirdi.
// Arayüz ve yoldaş sınıfı örneği
Ancak, Java 8'den beri, arayüzlere doğrudan statik yöntemler eklenebildiği için, bir yoldaş sınıfını ayrı olarak tanımlamak zorunda değilsiniz.
Giriş parametrelerine göre döndürülecek nesnenin sınıfını değiştirebilir.
Basitçe alt türler döndürmekle kalmayıp, ayrıca parametre değerine göre farklı alt türler de döndürebilir. Örneğin, puana göre MemberStatus'u farklı şekilde döndürmek istiyorsanız, aşağıdaki gibi statik fabrika yöntemini oluşturabilir ve içine karşılaştırma mantığını ekleyebilirsiniz.
public enum MemberStatus {
ADVANCED(80, 100),
INTERMEDIATE(50, 79),
BASIC(0, 49);
private final int minScore;
private final int maxScore;
MemberStatus(int minScore, int maxScore) {
this.minScore = minScore;
this.maxScore = maxScore;
}
public static MemberStatus of(int score) {
return Arrays.stream(values())
.filter(decideMemberStatus(score))
.findAny()
.orElseThrow(() -> new NoSuchElementException("İlgili MemberStatus nesnesi bulunamadı."));
}
private static Predicate decideMemberStatus(int score) {
return element -> element.minScore <= score && element.maxScore >= score;
}
}
@DisplayName("MemberStatus testi")
class MemberStatusTest {
@ParameterizedTest
@CsvSource(value = {"0:BASIC", "30:BASIC", "50:INTERMEDIATE", "70:INTERMEDIATE", "80:ADVANCED", "100:ADVANCED"}, delimiter = ':')
@DisplayName("Puanlara göre MemberStatus'u farklı şekilde döndürür.")
void of(int input, MemberStatus expected) {
assertThat(MemberStatus.of(input)).isEqualTo(expected);
}
Statik fabrika yöntemini yazdığınız sırada döndürülecek nesnenin sınıfının mevcut olması gerekmez.
Yukarıdaki cümlede, nesnenin sınıfıyazdığımız sınıf dosyasına karşılık gelir.
Referans olarak, Class> sınıf yükleyicisinin Class nesnesini yığına ayırdığı, yığına ait Class nesnesini gösterir. Bu Class nesnesi yazdığımız sınıfın çeşitli meta verilerini içerir.
package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
public abstract void getName();
public static StaticFactoryMethodType getNewInstance() {
StaticFactoryMethodType temp = null;
try {
Class> childClass = Class.forName("algorithm.dataStructure.StaticFactoryMethodTypeChild"); // yansıma
temp = (StaticFactoryMethodType) childClass.newInstance(); // yansıma
} catch (ClassNotFoundException e) {
System.out.println("Sınıf bulunamadı.");
} catch (InstantiationException e) {
System.out.println("Belleğe yüklenemedi.");
} catch (IllegalAccessException e) {
System.out.println("Sınıf dosyasına erişim hatası.");
}
return temp;
}
Yukarıdaki kodu incelerseniz, arayüz uygulama sınıfının nerede olduğunu sınıf nesnesiyle anlayabilir ve gerçek uygulama sınıfını başlatmak için yansıma tekniğini kullanabilirsiniz. Bu durumda, statik fabrika yöntemini yazdığınız sıradaStaticFactoryMethodTypeChild sınıfının mevcut olması gerekmez.
Uygulama sınıfı, static fabrika yöntemini kullandığınız zaman algoritma.dataStructure.StaticFactoryMethodTypeChild yolunda mevcut değilse, bir hata oluşur, ancak statik fabrika yöntemini yazdığınız zaman sorun olmadığı için esnek olduğu söylenebilir.
public interface Test {
int sum(int a, int b);
// Test bir arayüzdür ve uygulama sınıfı olmasa bile, statik fabrika yöntemi yazma sırasında sorun yoktur.
static Test create() {
return null;
}
}
public class Main {
public static void main(String[] args) {
Test test = Test.create();
System.out.println(test.sum(1, 2)); // NPE oluşur
}
Yansıma kullanmadan da aynı esnekliği elde edebilirsiniz. Uygulama sınıfı olmayan Test'in statik fabrika yöntemi olan create()'i incelerseniz, uygulama sınıfı olmasa bile yazma sırasında sorun yoktur. Tabii ki, gerçek kullanımda NPE oluşacaktır, bu nedenle daha sonra uygulama sınıfını döndürmeniz gerekir.
Bu esneklik, hizmet sağlayıcı çerçevelerini oluşturmanın temelidir ve bunun en iyi örneği JDBC'dir. JDBC hizmet sağlayıcı çerçevesinin sağlayıcısı, hizmetin uygulama sınıfı olup, bu uygulama sınıflarını istemciye sağlama rolünü çerçeve kontrol eder ve istemciyi uygulama sınıfından ayırarak (DIP)
- Hizmet sağlayıcı çerçevesinin bileşenleri
- Hizmet arayüzü
- Uygulamanın davranışını tanımlar
- JDBC'nin Bağlantısı
- Sağlayıcı kayıt API'si
- Sağlayıcılar uygulama sınıfını kaydeder
- JDBC'nin DriverManager.registerDriver()
- Servis erişim API'si
- Bir istemci, bir hizmet örneği istediğinde kullanılır ve koşul belirtilmezse, varsayılan uygulama sınıfı veya desteklenen uygulama sınıfları dairesel olarak döndürülür.
- Statik fabrika yöntemi karşılığı
- JDBC'nin DriverManager.getConnection()
- (İsteğe bağlı) Hizmet sağlayıcı arayüzü
- Bunun yokluğunda, her uygulama sınıfı için yansıma kullanılması gerekir
- JDBC'nin Sürücüsü
- Hizmet arayüzü
Hizmet sağlayıcı çerçeve deseni, köprü deseni, bağımlılık enjeksiyon çerçevesi gibi çeşitli varyasyonlara sahiptir.
Tipik JDBC örneği
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = null;
connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
Genel olarak JDBC yukarıdaki gibi yazılır. Class.forName() ile Driver'ın uygulama sınıflarından biri olan OracleDriver'ı kaydeder ve DriverManager.getConnection() ile Connection'ın uygulama sınıflarından biri olan OracleDriver için Connection elde eder.
Burada Connection bir servis arayüzüdür, DriverManager.getConnection() bir servis erişim API'sidir ve Driver bir servis sağlayıcı arayüzüdür. Ancak, sunucu kayıt API'si olan DriverManager.registerDriver() kullanılmadı. Yine de, yalnızca Class.forName() ile Driver uygulama sınıfı olan OracleDriver'ı kaydedebiliriz. Nasıl mümkün?
Class.forName()'nin çalışma prensibi
Belirtilen yöntem, fiziksel sınıf dosya adını bir parametre olarak alır ve JVM'den bu sınıfı yüklemesini ister. Daha sonra, sınıf yükleyici sınıfın meta verilerini yöntem alanına kaydeder ve bir Class nesnesini yığına ayırır. Ayrıca, sınıf yükleme işlemi tamamlandıktan sonra statik alanlar ve statik bloklar başlatılır ve bu sırada sağlayıcı kayıt API'si kullanılır.
public class OracleDriver implements Driver {
static {
defaultDriver = null;
Timestamp timestamp = Timestamp.valueOf("2000-01-01 00:00:00.0");
try {
if (defaultDriver == null) {
defaultDriver = new OracleDriver();
DriverManager.registerDriver(defaultDriver); // OracleDriver kayıt
}
} catch (RuntimeException runtimeexception) {
} catch (SQLException sqlexception) {
}
}
...
Gerçekte, OracleDriver'a bakarsanız, içerideki bir statik blokta DriverManager.registerDriver()'ı kullanarak Driver uygulama sınıfı olan OracleDriver'ı kaydettiğini görebilirsiniz.
DriverManager sınıfı analizi
public class DriverManager {
private DriverManager() {
}
private static final Map drivers = new ConcurrentHashMap();
public static final String DEFAULT_DRIVER_NAME = "default";
public static void registerDefaultPrivider(Driver d) {
System.out.println("Driver kaydedildi");
registerDriver(DEFAULT_DRIVER_NAME, d);
}
public static void registerDriver(String name, Driver d) {
drivers.put(name, d);
}
public static Connection getConnection() {
return getConnection(DEFAULT_DRIVER_NAME);
}
public static Connection getConnection(String name) {
Driver d = drivers.get(name);
if (d == null) throw new IllegalArgumentException();
return d.getConnection();
}
DriverManager sınıfı aslında çok daha karmaşıktır, ancak ana noktaları kısaca özetlersek yukarıdakine benzer. Yukarıda açıklandığı gibi, registerDriver()'ı OracleDriver'ın statik bloğunda çağırarak OracleDriver'ı kaydedebilir ve kullanıcının Connection uygulama sınıfını almak için getConnection()'ı çağırabiliriz.
Kullanıcı erişim API'si olan getConnetion()'ı daha yakından incelerseniz, Connection'ı Driver arayüzünden aldığını görürsünüz. Bir Driver servis arayüzü olmasaydı, istenen Connection uygulama sınıfını döndürmek için Class.forName() gibi yansıma kullanılabilirdi. O zaman, Connection uygulama sınıfının statik fabrika yazma zamanında mevcut olması gerekmez.
Bunun yerine Driver arayüzünü kullanır ve dinamik olarak Driver uygulama sınıfını kaydettikten sonra, bu Driver'a uygun Connection uygulama sınıfını kolayca elde edebiliriz.
Referans olarak, JDK kodunda DriverManager'ın getConnection() yönteminin gerçek kodunu analiz ettim, fazla ilgilenmiyorsanız, atlayabilirsiniz.
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
Öncelikle, genel statik getConnection() yöntemi çağrılır ve url, Properties ve CallerClass özel statik getConnection() yönteminin parametrelerine aktarılır. Bu sırada, Reflection.getCallerClass() çağrılan genel statik getConnection() yöntemini çağıran sınıfı döndürmek için kullanılır. Car sınıfı getConnection()'ı çağırdıysa, Reflection.getCallerClass() ile Class nesnesi elde edilebilir.
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();
}
}
if(url == null) {
throw new SQLException("Url null olamaz", "08001");
}
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
}
}
if (reason != null) {
throw reason;
}
throw new SQLException("Uygun sürücü bulunamadı "+ url, "08001");
callerCL bir sınıf yükleyici nesnesidir ve çağrıyı yapan veya geçerli iş parçacığının sınıf yükleyici tarafından oluşturulur. Daha sonra, mevcut uygulamanın kayıtlı sürücü listesi olan registeredDrivers'tan birer birer aDriver'ı alır. Ardından, bu sürücü isDriverAllowed() tarafından doğrulandıktan sonra, aDriver'a sahip bir Sürücü nesnesi ile bağlantı nesnesi elde edilir ve geri döndürülür. isDriverAllowed(), çağıran tarafta aDriver'ın mevcut olup olmadığını kontrol etmek için kullanılır.
JDBC çerçevesinin avantajları
JDBC çerçevesinin temel noktası, Driver, Connection arayüzü ve bu arayüzleri uygulayan gerçek uygulama sınıflarının tamamen ayrılmış bir şekilde sunulmasıdır. Kalıp oluşturmak için bir arayüz kullanılır ve bu kalıba uyan her uygulama sınıfı oluşturulur, bu da çok daha esnektir.
Bu nedenle, başka bir DBMS çıksa bile, o satıcı Driver ve Connection arayüzlerini uygulayarak sağlayabilir, böylece Java kullanan geliştiriciler diğer DBMS sürücülerini aynı API ile kullanabilirler.
Statik fabrika yönteminin dezavantajları
Miras alınırken, genel oluşturucuya ihtiyaç duyulur, bu nedenle yalnızca statik fabrika yöntemi sağlanırsa, alt sınıflar oluşturulamaz.
Ancak bu kısıtlama, kompozisyonu miras almaktan daha fazla teşvik etmesi ve değişmez türleri oluşturmak için bu kısıtlamanın uygulanması gerektiği için aslında bir avantaj olabilir.
Statik fabrika yöntemini bulmak programcılar için zor olabilir.
API açıklamalarında açıkça belirtilmediği için, geliştiriciler API belgelerini iyi yazmalı ve yaygın olarak bilinen kurallar izleyerek yöntem adlarını koymalıdır.
Statik fabrika yöntemi adlandırma kuralı
- from
- Bir parametre alır ve o türden bir örnek döndürür.
- Date date = Date.from(instant);
- of
- Birden çok parametre alır ve uygun türden bir örnek döndürür.
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- from ve of'nin daha ayrıntılı bir sürümü
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance 혹은 getInstance
- Belirtilen örneği döndürür, ancak aynı örnek olduğunu garanti etmez.
- StackWalker luke = StackWalker.getInstance(options);
- create 혹은 newInstance
- instance 혹은 getInstance ile aynıdır ancak her zaman yeni bir örnek oluşturacağını garanti eder.
- Object newArray = Array.newInstance(classObject, arraylen);
- getType
- getInstance ile aynıdır, ancak sınıf oluşturmayı belirten bir sınıf değil, farklı bir sınıfta fabrika yöntemi yazmayı belirten bir sınıftır.
- FileStore fs = Files.getFileStore(path);
- newType
- newInstance ile aynıdır, ancak sınıf oluşturmayı belirten bir sınıf değil, farklı bir sınıfta fabrika yöntemi yazmayı belirten bir sınıftır.
- BufferedReader br = Files.newBufferedReader(path);
- type
- getType ve newType'ın özlü bir sürümü
- List<Complaint> litany = Collections.list(legacyLitany);
Sonuç
Statik fabrika yöntemleri ve genel oluşturucuların her birinin kendi kullanım durumu vardır, bu nedenle uygun şekilde kullanılmalıdır.