Java kültürü hakkında söylenebilecek çok şey var, ancak şu anda karşı karşıya kalmanız durumunda, birkaç önemli husus olduğunu düşünüyorum:
- Kütüphane kodu bir kez yazılmıştır ancak çok daha sık kullanılmaktadır. Kütüphane yazma yükünü en aza indirmek güzel olsa da, uzun vadede kütüphane kullanma yükünü en aza indirecek şekilde yazmak daha yararlı olacaktır.
- Bu, kendi kendini belgeleyen türlerin harika olduğu anlamına gelir: yöntem adları, neler olduğunu ve bir nesneden ne elde ettiğinizi netleştirmenize yardımcı olur.
- Statik yazım, belirli hata sınıflarını ortadan kaldırmak için çok yararlı bir araçtır. Kesinlikle her şeyi çözmez (insanlar Haskell hakkında şaka yapmayı sever, bu da bir kez kodunuzu kabul etmek için tip sistemini aldığınızda, muhtemelen doğrudur), ancak bazı yanlış şeyler yapmayı imkansız hale getirir.
- Kütüphane kodu yazmak sözleşmeleri belirtmekle ilgilidir. Argümanınız ve sonuç türleriniz için arayüzler tanımlamak, sözleşmelerinizin sınırlarını daha net bir şekilde tanımlamanızı sağlar. Eğer bir şey bir tuple kabul ederse veya üretirse, gerçekte almanız veya üretmeniz gereken türden bir türe ait değildir ve bu tür bir jenerik tip üzerindeki kısıtlamalar konusunda çok az şey vardır (hatta doğru sayıda elemente sahip mi? beklediğiniz tipte bunlar mı?).
Alanlı "Struct" sınıfları
Diğer cevapların belirttiği gibi, sadece ortak alanlara sahip bir sınıfı kullanabilirsiniz. Bunları final yaparsanız, sabit bir sınıf elde edersiniz ve bunları yapıcı ile başlatırsınız:
class ParseResult0 {
public final long millis;
public final boolean isSeconds;
public final boolean isLessThanOneMilli;
public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
this.millis = millis;
this.isSeconds = isSeconds;
this.isLessThanOneMilli = isLessThanOneMilli;
}
}
Elbette, bu, belirli bir sınıfa bağlı olduğunuz anlamına gelir ve ayrıştırma sonucu üretmek veya tüketmek için gereken her şeyin bu sınıfı kullanması gerekir. Bazı uygulamalar için sorun değil. Diğerleri için bu bazı ağrılara neden olabilir. Java kodlarının çoğu sözleşmeleri tanımlamakla ilgilidir ve bu genellikle sizi arabirimlere götürür.
Başka bir tuzak, sınıf temelli bir yaklaşımla, alanları ortaya çıkardığınız ve bu alanların hepsinin değerleri olması gerektiğidir. Örneğin, isSeconds ve millis, isLessThanOneMilli doğru olsa bile daima bir değere sahip olmalıdır. LessThanOneMilli doğru olduğunda millis alanının değerinin yorumlanması ne olmalıdır?
Arayüzler olarak "Yapılar"
Arayüzlerde izin verilen statik yöntemlerle, çok fazla sözdizim yükü olmadan değişmez türler oluşturmak oldukça kolaydır. Örneğin, bahsettiğiniz sonuç yapısını şöyle bir şey olarak uygulayabilirim:
interface ParseResult {
long getMillis();
boolean isSeconds();
boolean isLessThanOneMilli();
static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
return new ParseResult() {
@Override
public boolean isSeconds() {
return isSeconds;
}
@Override
public boolean isLessThanOneMilli() {
return isLessThanOneMill;
}
@Override
public long getMillis() {
return millis;
}
};
}
}
Bu hala çok fazla kazan, kesinlikle katılıyorum, ama bunun da birkaç faydası var ve sanırım ana sorularınızın bazılarını cevaplamaya başlıyorlar.
Bu ayrıştırma sonucu gibi bir yapıyla, ayrıştırıcınızın sözleşmesi çok net bir şekilde tanımlanır. Python'da, bir bağ diğer bağdan gerçekten farklı değildir. Java'da statik yazım mevcuttur, bu nedenle zaten bazı hata sınıflarını ekarte ediyoruz. Örneğin, Python'da bir demet döndürüyorsanız ve onu döndürmek istiyorsanız (millis, isSeconds, isLessThanOneMilli), yanlışlıkla yapabilirsiniz:
return (true, 500, false)
ne zaman demek istedin:
return (500, true, false)
Bu tür bir Java arayüzü ile derleyemezsiniz:
return ParseResult.from(true, 500, false);
hiç. Yapmak zorundasın:
return ParseResult.from(500, true, false);
Bu genel olarak statik olarak yazılmış dillerin bir faydasıdır.
Bu yaklaşım aynı zamanda size hangi değerleri alabileceğinizi kısıtlama yeteneği vermeye başlıyor. Örneğin, getMillis () işlevini çağırırken, isLessThanOneMilli () 'nin doğru olup olmadığını kontrol edebilir ve eğer öyleyse, bu durumda millis için anlamlı bir değer olmadığından, bir IllegalStateException (örneğin) atabilirsiniz.
Yanlış İşi Yapmayı Zorlaştırmak
Yukarıdaki arayüz örneğinde, yine de isSeconds ve isLessThanOneMilli argümanlarını yanlışlıkla aynı tipte olduklarından takas etme probleminiz var.
Uygulamada, gerçekten TimeUnit ve süresinden yararlanmak isteyebilirsiniz, böylece şöyle bir sonuç elde edebilirsiniz:
interface Duration {
TimeUnit getTimeUnit();
long getDuration();
static Duration from(TimeUnit unit, long duration) {
return new Duration() {
@Override
public TimeUnit getTimeUnit() {
return unit;
}
@Override
public long getDuration() {
return duration;
}
};
}
}
interface ParseResult2 {
boolean isLessThanOneMilli();
Duration getDuration();
static ParseResult2 from(TimeUnit unit, long duration) {
Duration d = Duration.from(unit, duration);
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return false;
}
@Override
public Duration getDuration() {
return d;
}
};
}
static ParseResult2 lessThanOneMilli() {
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return true;
}
@Override
public Duration getDuration() {
throw new IllegalStateException();
}
};
}
}
Bu çok daha fazla kod haline gelmeye başlıyor , ancak yalnızca bir kez yazmanız gerekiyor (ve doğru bir şekilde belgelemiş olduğunuzu varsayarak), kodunuzu kullanarak bitiren kişilerin sonucun ne anlama geldiğini tahmin etmeleri gerekmez. Yanlışlıkla ne result[0]
zaman oldukları gibi şeyler yapamıyorum result[1]
. Hala olsun oluşturmak oldukça özlü örneklerini ve bunların dışına veri bütün bu zor ya değildir:
ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
ParseResult2 y = ParseResult2.lessThanOneMilli();
Bunun yanında sınıf temelli bir yaklaşımla da böyle bir şey yapabileceğinizi unutmayın. Sadece farklı durumlar için yapıcılar belirtin. Yine de diğer alanları neyin başlatacağı konusunda hala sorunlarınız var ve bunlara erişimi engelleyemiyorsunuz.
Bir başka cevap, Java’nın kurumsal tipi niteliğinin, çoğu zaman, zaten var olan diğer kütüphaneleri oluşturduğunuz veya başkalarının kullanması için kütüphaneler yazdığınız anlamına gelir. Sizin genel API bunun önlenebilir eğer sonuç türlerini deşifre belgeleri danışmanlık çok zaman gerektirmemelidir.
Yalnızca yazma kez bu yapıları, ancak oluşturmak onlara defalarca, bu nedenle hala (Alacağınız) özlü oluşturulmasını istiyoruz. Statik yazım, bunlardan elde ettiğiniz verilerin beklediğiniz gibi olmasını sağlar.
Şimdi, tüm söylenenler, basit tote veya listelerin çok fazla anlam ifade edebilecekleri yerler var. Bir şeyin dizisini döndürmede daha az ek yük olabilir ve eğer durum buysa (ve bu ek yük önemli, profillemeyle belirleyecekseniz), o zaman içten basit bir değer dizisi kullanmak çok mantıklı olabilir. Genel API’niz hala açıkça tanımlanmış türlere sahip olmalıdır.