'Final' anahtar kelimesi neden hiç faydalı olsun?


54

Görünüşe göre Java yaşları boyunca türev edilemez sınıfları ilan etme gücüne sahipti ve şimdi C ++ 'da da var. Ancak, SOLID'deki Aç / Kapat ilkesinin ışığında, bu neden faydalı olabilir? Bana göre, finalanahtar kelime kulağa benziyor friend- yasal, ancak kullanıyorsanız büyük olasılıkla tasarım yanlıştır. Lütfen türetilemeyen bir sınıfın mükemmel bir mimari veya tasarım modelinin bir parçası olacağı bazı örnekler verin.


43
Eğer bir sınıfa açıklama eklenmişse neden yanlış tasarlanmış olduğunu düşünüyorsunuz final? Birçok insan (ben dahil) her soyut olmayan sınıfı yapmanın iyi bir tasarım olduğunu düşünüyor final.
Benekli

20
Kalıtımın üstesinden gelmek için kompozisyonu tercih edin ve soyut olmayan her sınıfa sahip olun final.
Andy

24
Açık / kapalı ilke, bir anlamda, mantranın diğer sınıflardan miras alan sınıflardan miras kalan bir sınıf hiyerarşisi yapmak olduğu 20. yüzyıldan kalma bir anakronizmdir. Bu nesne yönelimli programlama öğretmek için güzeldi ama gerçek dünyadaki sorunlara uygulandığında karışık, sürdürülemez mesajlar yarattığı ortaya çıktı. Genişletilebilir bir sınıf tasarlamak zordur.
David Hammen

34
@DavidArno Saçmalama. Kalıtım, nesne yönelimli programlamanın olmazsa olmazıdır ve vaaz vermek gibi aşırı aşırı dogmatik bireyler kadar karmaşık veya dağınık bir yere yakın değildir. Diğer herkes gibi bir araçtır ve iyi bir programcı iş için doğru aleti kullanmayı bilir.
Mason Wheeler

48
İyileşen bir geliştirici olarak, zararlı davranışın kritik bölümlere girmesini engellemek için mükemmel bir araç olduğunu hatırlıyor gibiyim. Ayrıca, mirasın çeşitli şekillerde güçlü bir araç olduğunu hatırlıyor gibiyim. Neredeyse gasp'ın farklı araçlarının avantajları ve dezavantajları var ve mühendisler olarak yazılımlarımızı üretirken bu faktörleri dengelemek zorundayız!
corsiKa

Yanıtlar:


136

final niyetini ifade eder . Bir sınıf, yöntem veya değişken kullanıcısına "Bu öğenin değişmemesi gerekiyor ve değiştirmek istiyorsanız, mevcut tasarımı anlamadınız" diyor.

Bu önemlidir, çünkü program mimarisi, her sınıfın ve yazdığınız her yöntemin bir alt sınıf tarafından tamamen farklı bir şeyler yapması için değiştirilebileceğini tahmin etmek zorunda kalırsanız, gerçekten çok zor olacaktır . Hangi unsurların değişken olması gerektiği ve hangilerinin olmadığına karar vermek ve değişmezliği zorla uygulamak çok daha iyidir final.

Bunu ayrıca yorumlar ve mimarlık belgeleriyle de yapabilirsiniz, ancak derleyicinin gelecekteki kullanıcıların dokümanları okuyacaklarını ve bunlara uyacağını ümit etmekten daha iyi şeyler yapmasına izin vermek her zaman daha iyidir.


14
Ben de öyle olurdum. Ama yine de geniş çapta tekrar kullanılan bir temel sınıf (bir çerçeve veya medya kütüphanesi gibi) yazan herkes, uygulama programcılarının aklı başında bir şekilde davranmasını beklemekten daha iyi bilir. Onlar yıkmak, yanlış ve hatta olmasaydı şekillerde buluşu tahrif edeceğiz düşünce Eğer bir demir kavrama ile onu kapatmamız sürece mümkün idi.
Kilian Foth

8
@KilianFoth Tamam, ama dürüst olmak gerekirse, uygulama programcılarının yaptığı sorun ne?
coredump

20
@ coredump Kütüphanemi kötü kullanan kişiler kötü sistemler oluşturur. Kötü sistemler kötü ünleri doğurur. Kullanıcılar Kilian'ın harika kodunu Fred Random'un canavarca dengesiz uygulamasından ayırt edemez. Sonuç: Lisans ve müşteri programlamamı kaybediyorum. Kodunuzu kötüye kullanmayı zorlaştırmak, dolar ve sent meselesidir.
Kilian Foth

21
"Bu elemanın değişmemesi gerekiyor ve eğer değiştirmek istiyorsanız mevcut tasarımı anlamadınız" ifadesidir. inanılmaz derecede kibirli ve birlikte çalıştığım kütüphanelerin bir parçası olarak isteyeceğim tutum değil. Keşke her zaman için biraz param olsaydı, gülünç fazla kapsüllenmiş bir kütüphane , yazarın önemli bir kullanım vakasını öngöremediği için değiştirilmesi gereken bazı içsel durumları değiştirecek bir mekanizma bırakmadı .
Mason Wheeler

31
@JimmyB Temel kural: Yaratılışınızın ne için kullanılacağını bildiğinizi düşünüyorsanız, zaten yanılmışsınız. Bell telefonu esasen bir Muzak sistemi olarak düşündü. Kleenex, makyajın daha rahat çıkarılması amacıyla icat edildi. IBM Başkanı Thomas Watson, bir keresinde "Beş bilgisayar için belki de bir dünya pazarı olduğunu düşünüyorum" dedi. 2001’de PATRIOT Yasasını tanıtan Temsilci Jim Sensenbrenner, NSA’nın “PATRIOT eylem otoritesiyle” yaptıklarını yapmasını engellemek için özel olarak tasarlandığını belirtti . Ve böyle devam eder ...
Mason Wheeler

59

Kırılgan Temel Sınıf Problemini önler . Her sınıf, açık veya kesin garantiler ve değişmezler ile birlikte gelir. Liskov Değişim İlkesi, bu sınıfın tüm alt türlerinin de tüm bu garantileri sağlamaları gerektiğini zorunlu kılar. Ancak, biz kullanmazsak, bunu ihlal etmek gerçekten kolaydır final. Örneğin, bir şifre kontrol edelim:

public class PasswordChecker {
  public boolean passwordIsOk(String password) {
    return password == "s3cret";
  }
}

Bu sınıfın geçersiz kılınmasına izin verirsek, bir uygulama herkesi kilitleyebilir, diğeri herkese erişim izni verebilir:

public class OpenDoor extends PasswordChecker {
  public boolean passwordIsOk(String password) {
    return true;
  }
}

Bu genellikle normal değildir, çünkü alt sınıfların artık orijinaliyle çok uyumlu olmayan bir davranışı vardır. Sınıfın diğer davranışlarla genişletilmesi niyetinde olursak, bir Sorumluluk Zinciri daha iyi olurdu:

PasswordChecker passwordChecker =
  new DefaultPasswordChecker(null);
// or:
PasswordChecker passwordChecker =
  new OpenDoor(null);
// or:
PasswordChecker passwordChecker =
 new DefaultPasswordChecker(
   new OpenDoor(null)
 );

public interface PasswordChecker {
  boolean passwordIsOk(String password);
}

public final class DefaultPasswordChecker implements PasswordChecker {
  private PasswordChecker next;

  public DefaultPasswordChecker(PasswordChecker next) {
    this.next = next;
  }

  @Override
  public boolean passwordIsOk(String password) {
    if ("s3cret".equals(password)) return true;
    if (next != null) return next.passwordIsOk(password);
    return false;
  }
}

public final class OpenDoor implements PasswordChecker {
  private PasswordChecker next;

  public OpenDoor(PasswordChecker next) {
    this.next = next;
  }

  @Override
  public boolean passwordIsOk(String password) {
    return true;
  }
}

Daha karmaşık bir sınıf kendi yöntemlerini çağırdığında sorun daha belirgin hale gelir ve bu yöntemler geçersiz kılınabilir. Bazen bir veri yapısını güzel yazdırırken veya HTML yazarken bununla karşılaşıyorum. Her widget bazı widget'lardan sorumludur.

public class Page {
  ...;

  @Override
  public String toString() {
    PrintWriter out = ...;
    out.print("<!DOCTYPE html>");
    out.print("<html>");

    out.print("<head>");
    out.print("</head>");

    out.print("<body>");
    writeHeader(out);
    writeMainContent(out);
    writeMainFooter(out);
    out.print("</body>");

    out.print("</html>");
    ...
  }

  void writeMainContent(PrintWriter out) {
    out.print("<div class='article'>");
    out.print(htmlEscapedContent);
    out.print("</div>");
  }

  ...
}

Şimdi biraz daha stil katan bir alt sınıf oluşturuyorum:

class SpiffyPage extends Page {
  ...;


  @Override
  void writeMainContent(PrintWriter out) {
    out.print("<div class='row'>");

    out.print("<div class='col-md-8'>");
    super.writeMainContent(out);
    out.print("</div>");

    out.print("<div class='col-md-4'>");
    out.print("<h4>About the Author</h4>");
    out.print(htmlEscapedAuthorInfo);
    out.print("</div>");

    out.print("</div>");
  }
}

Şimdi bunun bir HTML sayfasını oluşturmak için iyi bir yol olmadığını göz ardı ederek, mizanpajı tekrar değiştirmek istersem ne olur? Bir SpiffyPageşekilde bu içeriği saran bir alt sınıf oluşturmak zorunda kalacağım . Burada görebildiğimiz , şablon yöntemi modelinin yanlışlıkla uygulanmasıdır. Şablon yöntemleri, geçersiz kılınması amaçlanan bir temel sınıftaki iyi tanımlanmış uzatma noktalarıdır.

Ve eğer temel sınıf değişirse ne olur? HTML içeriği çok fazla değişirse, bu, alt sınıfların sağladığı düzeni bozabilir. Bu nedenle temel sınıfı daha sonra değiştirmek gerçekten güvenli değildir. Bu, eğer tüm sınıflarınız aynı projedeyse, ancak, eğer temel sınıf diğer kişilerin üzerine kurulu, yayınlanan bir yazılımın parçasıysa, çok belirgindir.

Bu uzatma stratejisi tasarlandıysa, kullanıcının her bir parçanın nasıl oluşturulduğunu değiştirmesine izin verebilirdik. Her ikisi de, dışarıdan sağlanabilecek her blok için bir Strateji olabilir. Veya, Dekoratörleri yerleştirebiliriz. Bu, yukarıdaki koda eşdeğer, ancak çok daha açık ve çok daha esnek olacaktır:

Page page = ...;
page.decorateLayout(current -> new SpiffyPageDecorator(current));
print(page.toString());

public interface PageLayout {
  void writePage(PrintWriter out, PageLayout top);
  void writeMainContent(PrintWriter out, PageLayout top);
  ...
}

public final class Page {
  private PageLayout layout = new DefaultPageLayout();

  public void decorateLayout(Function<PageLayout, PageLayout> wrapper) {
    layout = wrapper.apply(layout);
  }

  ...
  @Override public String toString() {
    PrintWriter out = ...;
    layout.writePage(out, layout);
    ...
  }
}

public final class DefaultPageLayout implements PageLayout {
  @Override public void writeLayout(PrintWriter out, PageLayout top) {
    out.print("<!DOCTYPE html>");
    out.print("<html>");

    out.print("<head>");
    out.print("</head>");

    out.print("<body>");
    top.writeHeader(out, top);
    top.writeMainContent(out, top);
    top.writeMainFooter(out, top);
    out.print("</body>");

    out.print("</html>");
  }

  @Override public void writeMainContent(PrintWriter out, PageLayout top) {
    ... /* as above*/
  }
}

public final class SpiffyPageDecorator implements PageLayout {
  private PageLayout inner;

  public SpiffyPageDecorator(PageLayout inner) {
    this.inner = inner;
  }

  @Override
  void writePage(PrintWriter out, PageLayout top) {
    inner.writePage(out, top);
  }

  @Override
  void writeMainContent(PrintWriter out, PageLayout top) {
    ...
    inner.writeMainContent(out, top);
    ...
  }
}

( Dekoratör zincirinin üstünden geçecek topçağrıların yapıldığından emin olmak için ek parametre gereklidir writeMainContent. Bu, açık yineleme adı verilen bir alt sınıflandırma özelliğini taklit eder .)

Birden fazla dekoratörümüz varsa, şimdi onları daha özgürce karıştırabiliriz.

Mevcut işlevselliği biraz uyarlama arzusundan çok daha sık, mevcut bir sınıfın bir bölümünü yeniden kullanma arzusudur. Birinin, eşya ekleyebileceğiniz ve bunların hepsini yineleyebileceğiniz bir sınıf istediği bir durum gördüm. Doğru çözüm şuydu:

final class Thingies implements Iterable<Thing> {
  private ArrayList<Thing> thingList = new ArrayList<>();

  @Override public Iterator<Thing> iterator() {
    return thingList.iterator();
  }

  public void add(Thing thing) {
    thingList.add(thing);
  }

  ... // custom methods
}

Bunun yerine, bir alt sınıf oluşturdular:

class Thingies extends ArrayList<Thing> {
  ... // custom methods
}

Bu birdenbire tüm arayüzün bizim arayüzümüzün bir ArrayListparçası olduğu anlamına gelir . Kullanıcılar bazı şeyleri veya belirli endekslerdeki şeyleri yapabilir . Bu böyle mi planlandı? TAMAM. Ancak çoğu zaman, bütün sonuçları dikkatlice düşünmüyoruz.remove()get()

Bu nedenle tavsiye edilir

  • asla extenddikkatli düşüncesiz bir sınıf olmaz.
  • Sınıflarınızı her zaman finalgeçersiz kılmayı düşündüğünüz herhangi bir yöntemin kullanılması gibi işaretleyin .
  • Bir uygulamayı değiştirmek istediğiniz arayüzleri oluşturun, örneğin ünite testi için.

Bu “kural” ın çiğnenmesi gereken pek çok örnek var, ancak sizi iyi, esnek bir tasarıma yönlendiriyor ve temel sınıflardaki (veya alt sınıfın temel sınıfın bir örneği olarak istenmeyen kullanımları) istenmeyen değişikliklerden kaynaklanan hataları önlüyor. ).

Bazı diller daha katı uygulama mekanizmalarına sahiptir:

  • Tüm yöntemler varsayılan olarak kesindir ve açıkça olarak işaretlenmelidir. virtual
  • Arayüzü miras almayan, ancak yalnızca uygulamayı miras alan özel miras sağlarlar.
  • Temel sınıf yöntemlerinin sanal olarak işaretlenmesini ve tüm geçersiz kılmaların da işaretlenmesini gerektirir. Bu, bir alt sınıfın yeni bir yöntem tanımladığı, ancak aynı imzalı bir yöntemin daha sonra temel sınıfa eklendiği ancak sanal olarak tasarlanmadığı sorunları önler.

3
"Kırılgan temel sınıf probleminden" bahsettiğin için en az +100 hak ediyorsun. :)
David Arno

6
Burada verilen puanlardan ikna olmadım. Evet, kırılgan temel sınıf bir sorundur, ancak final bir uygulamayı değiştirmekle ilgili tüm sorunları çözmez. İlk örneğiniz kötü, çünkü PasswordChecker için olası tüm kullanım durumlarını bildiğinizi varsayıyorsunuz ("herkesi dışarıda bırakmak veya herkesin erişimine izin vermek ... uygun değil" - kim diyor?). Son "bu nedenle tavsiye edilebilir ..." listeniz gerçekten kötüdür - temel olarak hiçbir şeyi genişletmemeyi ve her şeyi nihai olarak işaretlemenizi savunuyorsunuz - ki bu da OOP'nin yararını, kalıtım ve kodların yeniden kullanılmasını tamamen ortadan kaldırıyor.
adelphus

4
İlk örneğiniz kırılgan temel sınıf sorununa bir örnek değil. Kırılgan temel sınıf probleminde, temel sınıftaki değişiklikler bir alt sınıfı kırar. Ancak bu örnekte, alt sınıfınız bir alt sınıfın sözleşmesini izlemez. Bunlar iki farklı problem. (Ayrıca, belirli koşullar altında bir şifre denetleyiciyi devre dışı bırakmanız (geliştirme için diyelim) gerçekten de mantıklıdır)
Winston Ewert

5
“Kırılgan temel sınıf sorununu önler” - aynı şekilde kendinizi öldürmek de aç olmaktan kaçınır.
user253751

9
@ immibis, yiyecek zehirlenmesini önlemek için yemekten kaçınmaya benzer. Elbette, asla yemek yemek bir sorun olmaz. Ancak yalnızca güvendiğiniz yerlerde yemek yemek çok mantıklı geliyor.
Winston Ewert

33

Hiç kimsenin henüz etkili olmayan, Java Bloch'un 2. Baskısından (en azından her Java geliştiricisi için okunması gereken) bahsetmemiş olması) şaşırdım . Kitaptaki 17 numaralı madde bunu detaylı olarak tartışıyor ve şöyle adlandırılıyor: " Miras için tasarım ve belge veya yasaklar ".

Kitaptaki tüm iyi önerileri tekrarlamayacağım, ancak bu belirli paragraflar alakalı görünüyor:

Peki ya sıradan somut sınıflar? Geleneksel olarak, ne son sınıf ne de alt sınıflandırma için tasarlanmış ve belgelenmiştir, ancak bu durum tehlikelidir. Böyle bir sınıfta her değişiklik yapıldığında, sınıfı genişleten müşteri sınıflarının kırılma olasılığı vardır. Bu sadece teorik bir problem değil. Kalıtım için tasarlanmamış ve belgelenmemiş bir final dışı somut sınıfın içindekileri değiştirdikten sonra, alt sınıflamaya ilişkin hata raporları alınması nadir değildir.

Bu sorunun en iyi çözümü, güvenli bir şekilde alt sınıflandırma için tasarlanmamış ve belgelenmemiş sınıflardaki alt sınıflamayı yasaklamaktır. Alt sınıflamayı yasaklamanın iki yolu vardır. İkisinin daha kolay olanı sınıf finalini ilan etmektir. Alternatif, tüm inşaatçıları özel veya paket özel yapmak ve inşaatçılar yerine halka açık statik fabrikalar eklemektir . Alt sınıfları dahili olarak kullanma esnekliği sağlayan bu alternatif, Madde 15'te tartışılmıştır. Her iki yaklaşım da kabul edilebilir.


21

Sebeplerden finalbiri yararlıdır, bir sınıfı, ana sınıfın sözleşmesini ihlal edecek şekilde alt sınıra alamayacağınızdan emin olmanızdır. Bu alt sınıflandırma, bir SOLID ihlali (en çok "L") olur ve bir sınıf yapmak finalbunu önler.

Tipik bir örnek, değişmez bir sınıfı alt sınıfı değişken hale getirecek şekilde alt sınıflamayı imkansız kılmaktır. Bazı durumlarda, böyle bir davranış değişikliği çok şaşırtıcı etkilere yol açabilir, örneğin bir haritadaki anahtarlar gibi bir şey kullandığınızda, gerçekte değiştirilebilen bir alt sınıf kullanırken, anahtarın değişmez olduğunu düşünebilirsiniz.

Java'da, Stringbu nesneler geçirilirken alt sınıflandırma ve değişken hale getirme (ya da birisi yöntemleri çağırdığında eve geri döndürme, böylece muhtemelen olası hassas verileri sistemden çıkarma) yapma şansınız olsaydı birçok ilginç güvenlik sorunu ortaya çıkarılabilirdi. sınıf yükleme ve güvenlikle ilgili bazı dahili kodların etrafında.

Final, aynı değişkeni bir yöntemde iki şey için tekrar kullanmak gibi basit hataların önlenmesinde de yardımcı olabilir. Scala'da, yalnızca valJava'daki son değişkenlere karşılık gelen ve aslında herhangi bir kullanımın kullanılması varveya son olmayan değişken şüphe ile bakılır.

Son olarak, derleyiciler, en azından teoride, bir sınıf veya yöntemin kesin olduğunu bildiklerinde bazı ekstra optimizasyonlar yapabilir, çünkü bir final sınıfında bir yöntem çağırdığınızda, tam olarak hangi yöntemin çağrılacağını bilirsiniz ve gitmek zorunda kalmazsınız. devralmayı kontrol etmek için sanal yöntem tablosu aracılığıyla.


6
Son olarak, derleyiciler en azından teoride => kişisel olarak Clang'ın devirtualization pass'ını gözden geçirdim ve pratikte kullanıldığını onaylıyorum.
Matthieu M.

Ancak derleyici önceden belirlenmiş olup olmadığına bakmaksızın kimsenin bir sınıfı veya yöntemi geçersiz kılmadığını söyleyemez mi?
JesseTG

3
@JesseTG Muhtemelen, bir kerede tüm koda erişimi varsa. Yine de ayrı dosya derlemesi ne durumda?
Monica'yı

3
@JesseTG devirtualization veya (monomorphic / polymorphic) satır içi önbellekleme, JIT derleyicilerinde yaygın bir tekniktir, çünkü sistem şu anda hangi sınıfların yüklü olduğunu bilir ve geçersiz kılma yöntemlerinin varsayılmasının yanlış olduğu ortaya çıkarsa kodu deoptize edebilir. Ancak, önceden bir derleyici derleyemez. Bir Java sınıfını ve bu sınıfı kullanan kodu derlerken, daha sonra bir alt sınıfı derleyebilir ve alıcı koda bir örnek iletebilirim. En basit yol, sınıf yolunun önüne başka bir kavanoz eklemektir. Derleyici bunların hepsini bilemez, çünkü bu çalışma zamanında gerçekleşir.
16.00

5
@MTilsted Dizgelerin değiştirilemez olduğunu varsayabilirsiniz , zira yansıtma yoluyla mutasyon geçiren dizgiler a SecurityManager. Çoğu program kullanmaz, ancak güvenilir olmayan bir kod da çalıştırmazlar. +++ Dizelerin değişmez olduğunu varsaymanız gerekir , aksi takdirde sıfır güvenlik ve bir dizi isteğe bağlı hata elde edersiniz. Java dizelerinin üretken kodda değişebileceğini varsayan herhangi bir programcı kesinlikle delilik.
maaartinus

7

İkinci sebep ise performans . İlk sebep, bazı sınıfların sistemin çalışmasına izin vermek için değiştirilmemesi gereken önemli davranışlara veya durumlara sahip olmalarıdır. Örneğin, bir "PasswordCheck" sınıfım varsa ve o sınıfı oluşturmak için bir güvenlik uzmanı ekibi tuttum ve bu sınıf iyi çalışılmış ve tanımlanmış protokollerle yüzlerce ATM ile iletişim kuruyor. Üniversiteden yeni çıkmış, işe alınan bir erkeğin, yukarıdaki sınıfı uzatan bir "TrustMePasswordCheck" sınıfı yapmasına izin vermek, sistemime çok zarar verebilir; Bu yöntemlerin geçersiz kılmaması gerekiyordu, hepsi bu.



JVM, dersleri final ilan edilmemiş olsalar bile, alt sınıfları yoksa, final olarak değerlendirecek kadar akıllıdır.
user253751

'Performans' iddiası pek çok kez ispatlandığı için oy vermedi.
Paul Smith,

7

Bir sınıfa ihtiyacım olduğunda, bir sınıf yazacağım. Alt sınıflara ihtiyacım yoksa, alt sınıfları umursamıyorum. Sınıfımın amaçlandığı gibi davrandığından ve sınıfı kullandığım yerlerin sınıfın amaçlandığı şekilde davrandığını varsaydığımdan emin olun.

Herhangi biri sınıfımı alt sınıflamak isterse, olanların sorumluluğunu tamamen reddetmek istiyorum. Bunu, sınıfı “final” yaparak başardım. Eğer alt sınıfa girmek istiyorsanız, sınıfı yazarken alt sınıfa dahil etmediğimi unutmayın. Bu yüzden sınıf kaynak kodunu almanız, “final” i silmeniz gerekir ve ondan sonra olan her şey sizin sorumluluğunuzdadır .

Sence bu "nesne yönelimli değil" mi? Yapması gerekeni yapan bir sınıf yapmam için bana para verildi. Kimse alt sınıflara girebilecek bir sınıf yapmam için bana para vermedi. Sınıfımı yeniden kullanılabilir hale getirmek için para alırsanız, bunu yapmaktan memnuniyet duyarız. "Final" anahtar kelimesini kaldırarak başlayın.

(Bunun dışında, "final" genellikle önemli optimizasyonlara izin verir. Örneğin, Swift "genel" sınıfında veya genel sınıf yönteminde, derleyicinin bir yöntem çağrısının hangi kodu uygulayacağını tam olarak bildiği anlamına gelir, ve dinamik gönderimi statik gönderim (küçük fayda) ile değiştirebilir ve genellikle statik gönderimi satır içi (muhtemelen büyük yarar) ile değiştirebilir.

adelphus: "Sınıflandırmak, kaynak kodunu almak, 'final'i kaldırmak ve sizin sorumluluğunuzdadır" hakkında ne anlamak zor? "final", "adil uyarıya" eşittir.

Ve ben değil yeniden kullanılabilir kod yapmaya ödedi. Yapması gereken şeyi yapan kod yazmam için bana para verildi. İki benzer kod parçasını yapmam için para ödüyorsam, ortak parçaları çıkarırım çünkü bu daha ucuzdur ve zamanımı boşa harcamam için bana para ödenmez. Yeniden kullanılmayan kodun tekrar kullanılabilir hale getirilmesi zaman kaybıdır.

M4ks: Her zaman dışarıdan erişilmesi gerekmeyen her şeyi özel yapıyorsun . Yine, alt sınıfa geçmek istiyorsanız, kaynak kodunu alır, ihtiyaç duyduğunuzda şeyleri "korumalı" olarak değiştirir ve yaptığınız şeyin sorumluluğunu üstlenirsiniz. Özel olarak işaretlediğim şeylere erişmeniz gerektiğini düşünüyorsanız, ne yaptığınızı daha iyi bilirsiniz.

Her ikisi de: Alt sınıflama, yeniden kullanma kodunun küçük, küçük bir kısmıdır. Alt sınıflandırma olmadan uyarlanabilecek yapı taşları oluşturmak, "final" den çok daha güçlü ve büyük fayda sağlar çünkü blokların kullanıcıları elde ettikleri şeye güvenebilir.


4
-1 Bu cevap yazılım geliştiricilerde yanlış olan her şeyi açıklar. Birisi sınıfınızı alt sınıflara göre yeniden kullanmak isterse, bırakmalarını sağlayın. Neden kullanmaları (veya kötüye kullanmaları) sizin sorumluluğunuzda? Temel olarak, kullandığınız son af olarak size k, dersimi kullanmıyoruz. * "Kimse alt sınıflara girebilecek bir sınıf yapmam için bana para vermedi" . Ciddi misin? İşte bu nedenle yazılım mühendisleri kullanılıyor - sağlam, yeniden kullanılabilir kodlar oluşturmak için.
adelphus

4
-1 Sadece her şeyi özel yapın, böylece kimse alt sınıflamayı düşünmez bile ..
M4ks

3
@ adelphus Bu cevabın ifadeleri açık sözlü olsa da, sert bir tasarıma sahip olmak, "yanlış" bir bakış açısı değildir. Aslında, bu soruya verilen cevapların çoğunluğu ile aynı bakış açısı ancak daha az klinik bir tonla aynıdır.
NemesisX00 13:16

'Final'i kaldırabileceğinizi belirttiğiniz için +1. Kodunuzun tüm olası kullanımlarını bildiğinizi iddia etmek kibirlidir. Yine de, bazı olası kullanımları sürdüremeyeceğinizi ve bu kullanımların bir çatal bulundurmayı gerektireceğini açıkça belirtmek alçak gönüllüdür.
gmatht

4

Bir platform için SDK'nın aşağıdaki sınıfı taşıdığını düşünelim:

class HTTPRequest {
   void get(String url, String method = "GET");
   void post(String url) {
       get(url, "POST");
   }
}

Bir uygulama bu sınıfı alt sınıflar:

class MyHTTPRequest extends HTTPRequest {
    void get(String url, String method = "GET") {
        requestCounter++;
        super.get(url, method);
    }
}

Her şey yolunda ve gayet iyi, ancak SDK üzerinde çalışan bir kişi bir yöntemi geçmenin getaptalca olduğuna karar veriyor ve arayüzü geriye dönük uyumluluğu zorlamak için daha emin kılıyor.

class HTTPRequest {
   @Deprecated
   void get(String url, String method) {
        request(url, method);
   }

   void get(String url) {
       request(url, "GET");
   }
   void post(String url) {
       request(url, "POST");
   }

   void request(String url, String method);
}

Yukarıdan gelen uygulama yeni SDK ile yeniden derlenene kadar her şey yolunda görünüyor. Birdenbire, geçersiz kılma yöntemi artık çağrılmıyor ve istek sayılmıyor.

Bu, kırılgan temel sınıf problemi olarak adlandırılır, çünkü görünüşte innocous değişiklik bir alt sınıf kırılmasına neden olur. Sınıf içinde hangi yöntemlerin çağrıldığı her zaman bir alt sınıfın kırılmasına neden olabilir. Bu eğilim, neredeyse tüm değişikliklerin bir alt sınıfın kırılmasına neden olabileceği anlamına gelir.

Final, kimsenin sınıfınızı almasını engeller. Bu şekilde, sınıf içindeki hangi yöntemlerin bir yerde birisinin tam olarak hangi yöntem çağrıları yapıldığına bağlı olduğu endişesi duymadan değiştirilebilir.


1

Kesin olarak etkili olması, sınıfınızın gelecekte mirasa dayalı sınıfları (çünkü hiçbiri olmadığından) veya sınıfın iş güvenliği ile ilgili herhangi bir konuyu etkilemeden gelecekteki değişimin güvenli olduğu anlamına gelir (bence bir alandaki son anahtar kelimenin engellediği durumlar vardır. bazı iş parçacığı tabanlı yüksek jinx).

Final, sınıfınızın, başkalarının kurallarına dayanarak, sizin temelinize dayanan davranış kurallarında herhangi bir istenmeyen değişiklik olmadan çalışma şeklini değiştirmekte özgür olduğunuz anlamına gelir.

Örnek olarak, HobbitKiller adında bir sınıf yazıyorum, ki bu harika, çünkü tüm hobiler hileci ve muhtemelen ölmeli. Kazıyın, kesinlikle ölmeleri gerekiyor.

Bunu bir temel sınıf olarak kullanıyorsunuz ve bir alev makinesi kullanmak için harika bir yeni yöntem ekliyorsunuz, ancak sınıfımı temel olarak kullanıyorsunuz çünkü hobileri hedef almak için harika bir yöntemim var (hileci olmanın yanı sıra, hızlılar), alev makinenizi hedeflemenize yardımcı olmak için kullanıyorsunuz.

Üç ay sonra, hedefleme yöntemimin uygulamasını değiştirdim. Şimdi, kitaplığınızı yükselttikten sonraki bir noktada, sizin de haberi olmadan, sınıfınızın gerçek çalışma zamanı uygulaması temelde, bağımlı olduğunuz süper sınıf yöntemindeki bir değişiklik nedeniyle değişti (ve genellikle kontrol etmeyin).

Bu yüzden, bilinçli bir geliştirici olmam ve sınıfımı kullanarak geleceğe doğru hobbit ölümünü garantilemem için, genişletilebilecek herhangi bir sınıfta yaptığım değişikliklerde çok, çok dikkatli olmalıyım.

Sınıfı özellikle genişletmeyi planladığım durumlar dışında, uzatma yeteneğimi ortadan kaldırarak kendimi (ve umarım diğerlerini) birçok başağrısından kurtarıyorum.


2
Hedefleme yönteminiz değişecekse neden herkese açık hale getirdiniz? Ve eğer bir sınıfın davranışını önemli ölçüde değiştirirseniz, aşırı korumacı olmak yerine başka bir sürüme ihtiyacınız olacaktırfinal
M4ks

Bunun neden değişeceği hakkında hiçbir fikrim yok. Hobbitler hileci ve değişim gerekli. Mesele şu ki, bunu bir son olarak inşa edersem, diğer insanları benim değişikliklerimin kodlarına bulaşmasını önleyen kalıtımdan korurum.
Scott Taylor

0

Kullanımı finalhiçbir şekilde SOLID ilkelerinin ihlali değildir. Ne yazık ki, Açık / Kapalı Prensibi ("yazılım varlıklarının genişletme için açık, ancak değişiklik için kapalı olması" ")" bir sınıfı değiştirmek, onu alt sınıflamak ve yeni özellikler eklemek "olarak yorumlamak son derece yaygındır. Aslen kastedilen bu değildir ve genellikle hedeflerine ulaşmak için en iyi yaklaşım olmadığı düşünülmektedir.

OCP'ye uymanın en iyi yolu, uzatma noktalarını bir sınıfa tasarlamak, özellikle nesneye bir bağımlılık enjekte ederek parametrelendirilen soyut davranışlar sağlayarak (örneğin, Strateji tasarım modelini kullanarak) sağlamaktır. Bu davranışlar, yeni uygulamaların mirasa dayanmaması için bir arayüz kullanacak şekilde tasarlanmalıdır.

Başka bir yaklaşım ise, sınıfınızı ortak API'si ile soyut bir sınıf (veya arayüz) olarak uygulamaktır. Daha sonra aynı istemcilere bağlanabilecek tamamen yeni bir uygulama üretebilirsiniz. Yeni arayüzünüz orijinalle tamamen benzer davranışlar gerektiriyorsa, şunlardan birini yapabilirsiniz:

  • Orijinalin mevcut davranışını tekrar kullanmak için Dekoratör tasarım desenini kullanmak veya
  • Bir yardımcı nesnede tutmak istediğiniz davranışın parçalarını yeniden düzenleyin ve yeni uygulamanızda aynı yardımcıyı kullanın (yeniden düzenleme değişiklik değildir).

0

Bana göre bu bir tasarım meselesi.

Sanırım çalışanlar için maaşları hesaplayan bir programım var. Ülkeye göre 2 tarih arasında çalışma günlerini geri döndüren bir sınıfım varsa (her ülke için bir sınıf), o finali koyacağım ve her işletmenin yalnızca takvimleri için ücretsiz bir gün sağlamaları için bir yöntem sunacağım.

Neden? Basit. Bir geliştiricinin, WorkingDaysUSA sınıfındaki bir temel sınıfı WorkingDaysUSAmyCompany'de devralmak istediğini varsayalım ve girişimcinin grev / bakım nedeniyle kapalı olacağını düşünecek şekilde değiştiriniz.

Müşterilerin siparişleri ve teslimatı için yapılan hesaplamalar gecikmeyi yansıtacak ve çalışma zamanında WorkingDaysUSAmyCompany.getWorkingDays () öğesini çağırdıklarında buna göre çalışacaktır, ancak tatil zamanını hesapladığımda ne olur? 2. Mars'ı herkes için bir tatil olarak eklemeli miyim? Hayır. Ancak programcı miras kullandığından ve ben sınıfı korumadığım için bu karışıklığa yol açabilir.

Veya diyelim ki sınıfta miras alıyorlar ve bu şirketin cumartesi günü yarı yarıya çalıştıkları ülkede cumartesi günü çalışmadıklarını yansıtacak şekilde değiştiriyorlar. Sonra bir deprem, elektrik krizi veya bazı durumlar, cumhurbaşkanının Venezüella’da olduğu gibi çalışmayan 3 gün ilan etmesini sağlıyor. Miras alınan sınıfın yöntemi her Cumartesi zaten çıkarıldıysa, orijinal sınıftaki değişiklikler aynı gün iki kez çıkarılabilir. Her müşterideki her bir alt sınıfa gitmem ve tüm değişikliklerin uyumlu olduğunu doğrulamam gerekirdi.

Çözüm? Sınıfın finalini yapın ve bir addFreeDay (companyID my company, Date freeDay) yöntemi sağlayın. Bu şekilde bir WorkingDaysCountry sınıfını çağırdığınızda alt sınıf değil ana sınıfınız olduğundan emin olursunuz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.