Soyut bir sınıfa hangi kod dahil edilmelidir?


10

Son zamanlarda soyut sınıfların kullanımı konusunda sıkıntı yaşıyorum.

Bazen soyut bir sınıf önceden yaratılır ve türetilmiş sınıfların nasıl çalışacağının bir şablonu olarak çalışır. Bu, az çok, yüksek düzeyde işlevsellik sağladıkları, ancak türetilmiş sınıflar tarafından uygulanacak belirli ayrıntıları bıraktığı anlamına gelir. Soyut sınıf, bazı soyut yöntemleri uygulayarak bu detaylara olan ihtiyacı tanımlar. Bu gibi durumlarda, soyut bir sınıf, bir taslak, işlevsellik hakkında yüksek düzeyde bir açıklama veya ona ne derseniz diye çalışır. Tek başına kullanılamaz, ancak üst düzey uygulamanın dışında bırakılan ayrıntıları tanımlamak için uzmanlaştırılmalıdır.

Diğer bazı zamanlarda, soyut sınıfın bazı "türetilmiş" sınıflar oluşturulduktan sonra yaratıldığı (ana / soyut sınıf orada olmadığı için, henüz türetilmedi, ancak ne demek istediğimi biliyorsun). Bu durumlarda, soyut sınıf genellikle geçerli türetilmiş sınıfların içerdiği her türlü ortak kodu koyabileceğiniz bir yer olarak kullanılır.

Yukarıdaki gözlemleri yaptıktan sonra, bu iki durumdan hangisinin kural olması gerektiğini merak ediyorum. Şu anda türetilmiş tüm sınıflarda ortak oldukları için soyut sınıfa kadar herhangi bir ayrıntı kabartılmalı mıdır? Üst düzey bir işlevselliğin parçası olmayan ortak kod olmalı mı?

Soyut sınıfın kendisi için bir anlamı olmayabilecek olan kod, sadece türetilmiş sınıflar için ortak olduğu için orada olmalı mıdır?

Bir örnek vereyim: Soyut sınıf A'nın bir () yöntemi ve bir soyut yöntemi aq () vardır. Her iki türetilmiş AB ve AC sınıfında aq () yöntemi, b () yöntemini kullanır. B () A'ya mı taşınmalı? Eğer evet ise, o zaman birisinin sadece A'ya bakması durumunda (AB ve AC'nin orada olmadığını varsayalım), b () 'nin varlığı pek mantıklı olmaz! Bu kötü bir şey mi? Birisi soyut bir sınıfa bakıp türetilmiş sınıfları ziyaret etmeden neler olduğunu anlayabilmeli mi?

Dürüst olmak gerekirse, bunu sorma anında, türetilmiş sınıflara bakmak zorunda kalmadan mantıklı bir soyut sınıf yazmanın temiz kod ve temiz mimari meselesi olduğuna inanıyorum. Any Her tür kod için bir döküm gibi davranan soyut bir sınıf fikrini gerçekten sevmiyorum, türetilmiş tüm sınıflarda yaygındır.

Ne düşünüyorsun / pratik yapıyorsun?


7
Neden bir "kural" olmalı?
Robert Harvey

1
Writing an abstract class that makes sense without having to look in the derived classes is a matter of clean code and clean architecture.-- Neden? Bir uygulama tasarlarken ve geliştirirken, birkaç sınıfın doğal olarak soyut bir sınıfa yeniden yansıtılabilen ortak işlevselliğe sahip olduğunu keşfetmem mümkün değil mi? Türetilmiş sınıflar için herhangi bir kod yazmadan önce bunu her zaman tahmin edebilecek kadar basiretli olmam gerekir mi? Eğer bu kadar zekice olmazsam, böyle bir yeniden düzenleme yapmam yasak mı? Kodumu atmam ve baştan başlamam gerekir mi?
Robert Harvey

Üzgünüm, eğer yanlış anlaşılmış olsaydım! (Şu an için) hissettiğimi daha iyi bir uygulama olarak söylemeye çalışıyordum ve mutlak bir kural olması gerektiğini ima etmiyordum. Dahası, soyut sınıfa ait herhangi bir kodun önceden yazılması gerektiğini ima etmiyorum. Pratikte, soyut bir sınıfın üst düzey kodla (türetilenler için bir şablon görevi gören) ve düşük seviyeli kodla nasıl sonuçlandığını açıklıyordum. türetilmiş sınıflar).
Alexandros Gougousis

@RobertHarvey bir kamu üssü sınıfı için, türetilmiş sınıflara bakmanız yasaktır. İç sınıflar için fark etmez.
Frank Hileman

Yanıtlar:


4

Bir örnek vereyim: Soyut sınıf A'nın bir () yöntemi ve bir soyut yöntemi aq () vardır. Her iki türetilmiş AB ve AC sınıfında aq () yöntemi, b () yöntemini kullanır. B () A'ya mı taşınmalı? Eğer evet ise, o zaman birisinin sadece A'ya bakması durumunda (AB ve AC'nin orada olmadığını varsayalım), b () 'nin varlığı pek mantıklı olmaz! Bu kötü bir şey mi? Birisi soyut bir sınıfa bakıp türetilmiş sınıfları ziyaret etmeden neler olduğunu anlayabilmeli mi?

Sorduğunuz şey nereye yerleştirilecek b()ve başka bir anlamda bir soru, ve Aiçin süper sınıf olarak en iyi seçim olup olmadığıdır . ABAC

Görünüşe göre üç seçenek var:

  1. b()ikisinde de ayrıl ABveAC
  2. bir ara sınıf oluşturmak ABAC-Parentdevralır olduğu Ave bu tanıtır b(), ve daha sonra, hemen süper sınıf olarak kullanılır ABveAC
  3. koymak b()içinde A(başka gelecek sınıfı olmadığını bilmeden ADisteyecektir b()veya değil)

  1. KURU olmamaktan muzdarip .
  2. YAGNI muzdarip .
  3. yani, bu bırakıyor.

Başka bir sınıf kadar ADistemiyor b()sunulur kendisi, (3) doğru bir seçim gibi görünüyor.

Bir ADhediye olarak, o zaman (2) 'deki yaklaşıma yeniden bakabiliriz - sonuçta yazılım!


7
4. seçenek var. Koymayın b()herhangi sınıfta. Tüm verilerini bağımsız değişken olarak alan ve her ikisine birden sahip olan ABve AConu çağıran ücretsiz bir işlev yapın . Bu, eklediğinizde onu taşımanız veya daha fazla sınıf oluşturmanız gerekmediği anlamına gelir AD.
user1118321

1
@ user1118321, kutunun dışında mükemmel, güzel düşünme. b()Örnek-uzun ömürlü bir duruma ihtiyaç duyulmuyorsa özellikle mantıklıdır .
Erik Eidt

(3) 'te beni rahatsız eden şey, soyut sınıfın artık kendi kendine yeten bir davranış parçasını tanımlamamasıdır. Bu bit ve kod parçaları ve bu ve türetilmiş sınıflar arasında ileri geri gitmeden soyut sınıf kodu anlayamıyorum.
Alexandros Gougousis

Soyut olmak yerine acçağıran bir baz / varsayılan yapabilir misiniz ? bac
Erik Eidt

3
Bana göre en önemli soru, hem AB hem de AC'nin aynı yöntemi b () kullanması. Eğer bu sadece tesadüf ise, AB ve AC'de iki benzer uygulama bırakırdım. Ama muhtemelen AB ve AC için bazı ortak soyutlamalar var. Bana göre, DRY kendi başına bir değer değil, muhtemelen bazı yararlı soyutlamaları kaçırdığınıza dair bir ipucu.
Ralf Kleberhoff

2

Soyut bir sınıf, uygun olduğu için soyut sınıfa atılan çeşitli işlevler veya veriler için boşaltma alanı değildir.

En güvenilir ve uzatılabilir nesne yönelimli yaklaşım gibi görünen tek kural, " Kompozisyonu kalıtım yerine tercih etmektir" . Soyut bir sınıf muhtemelen en iyi herhangi bir kod içermeyen bir arayüz belirtimi olarak düşünülür.

Soyut sınıfla birlikte gelen bir tür kütüphane yöntemi olan bir yönteminiz varsa, soyut bir sınıftan türetilen sınıfların genellikle ihtiyaç duyduğu bazı işlevsellik veya davranışları ifade etmenin en olası yolu olan bir yöntem, soyut sınıf ile bu yöntemin kullanılabildiği diğer sınıflar arasındaki sınıf. Bu yeni sınıf, bu yöntemi sağlayarak belirli bir uygulama alternatifini veya yolunu tanımlayan soyut sınıfın belirli bir örneğini sağlar.

Soyut bir sınıf fikri, soyut sınıfı gerçekte uygulayan türetilmiş sınıfların hizmet veya davranış olarak sağladıkları şeylerin soyut bir modeline sahip olmaktır. Kalıtımın aşırı kullanımı kolaydır ve çoğu zaman en faydalı sınıflar bir mixin deseni kullanan çeşitli sınıflardan oluşur .

Bununla birlikte, her zaman değişim soruları, neyin değişeceği ve nasıl değişeceği ve neden değişeceği vardır.

Kalıtım kırılgan ve kırılgan kaynak gövdelerine yol açabilir (ayrıca bakınız Kalıtım: sadece kullanmayı bırakın! ).

Kırılgan temel sınıf sorunu, temel sınıfların (üst sınıfların) "kırılgan" olduğu düşünülen nesne yönelimli programlama sistemlerinin temel bir mimari problemidir, çünkü türetilmiş sınıflar tarafından miras alındığında, temel sınıfta görünürde güvenli değişiklikler türetilmiş sınıfların arızalanmasına neden olabilir . Programcı, temel sınıf yöntemlerini tek başına inceleyerek bir temel sınıf değişikliğinin güvenli olup olmadığını belirleyemez.


Herhangi bir OOP kavramının yanlış veya aşırı kullanılması veya kötüye kullanılması durumunda sorunlara yol açabileceğinden eminim! Dahası, b () 'nin bir kütüphane yöntemi olduğu varsayımını yapmak (örneğin, diğer bağımlılıkları olmayan saf bir işlev) sorumun kapsamını daraltır. Bundan kaçınalım.
Alexandros Gougousis

@AlexandrosGougousis Bunun b()saf bir işlev olduğunu varsaymıyorum . Bir functoid veya bir şablon veya başka bir şey olabilir. Saf işlev, COM nesnesi veya çözüm için sağladığı işlevsellik için kullanılan her ne olursa olsun bazı bileşen anlamı olarak "kütüphane yöntemi" ifadesini kullanıyorum.
Richard Chambers

Üzgünüm, net olmasaydım! Saf işlevin birçok örnekten biri olarak bahsetmiştim (daha fazlasını verdiniz).
Alexandros Gougousis

1

Sorunuz soyut sınıflara bir ya da yaklaşım önermektedir. Ancak bunları araç kutunuzdaki başka bir araç olarak görmeniz gerektiğini düşünüyorum. Ve sonra soru şu olur: Soyut sınıflar hangi iş / problemler için doğru araçtır?

Mükemmel bir kullanım örneği, şablon yöntemi modelini uygulamaktır . Tüm değişmez mantığı soyut sınıfa, varyant mantığı da alt sınıflarına koydunuz. Paylaşılan mantığın kendi içinde eksik ve işlevsel olmadığını unutmayın. Çoğu zaman, birkaç adımın her zaman aynı olduğu, ancak en az bir adımın değiştiği bir algoritma uygulamakla ilgilidir. Bu adımı soyut sınıf içindeki işlevlerden birinden çağrılan soyut bir yöntem olarak koyun.

Bazen soyut bir sınıf önceden yaratılır ve türetilmiş sınıfların nasıl çalışacağının bir şablonu olarak çalışır. Bu, az çok, yüksek düzeyde işlevsellik sağladıkları, ancak türetilmiş sınıflar tarafından uygulanacak belirli ayrıntıları bıraktığı anlamına gelir. Soyut sınıf, bazı soyut yöntemleri uygulayarak bu detaylara olan ihtiyacı tanımlar. Bu gibi durumlarda, soyut bir sınıf, bir taslak, işlevsellik hakkında yüksek düzeyde bir açıklama veya ona ne derseniz diye çalışır. Tek başına kullanılamaz, ancak üst düzey uygulamanın dışında bırakılan ayrıntıları tanımlamak için uzmanlaştırılmalıdır.

Ben ilk örneğinizin aslında şablon yöntemi deseninin bir açıklaması olduğunu düşünüyorum (yanılıyorsam beni düzelt), bu yüzden bu soyut sınıfların mükemmel geçerli bir kullanım durumu olarak kabul ediyorum.

Diğer bazı zamanlarda, soyut sınıfın bazı "türetilmiş" sınıflar oluşturulduktan sonra yaratıldığı (ana / soyut sınıf orada olmadığı için, henüz türetilmedi, ancak ne demek istediğimi biliyorsun). Bu durumlarda, soyut sınıf genellikle geçerli türetilmiş sınıfların içerdiği her türlü ortak kodu koyabileceğiniz bir yer olarak kullanılır.

İkinci örneğiniz için, soyut sınıfları kullanmanın en uygun seçim olmadığını düşünüyorum, çünkü paylaşılan mantık ve çoğaltılmış kodla başa çıkmak için üstün yöntemler var. Let Diyelim ki soyut sınıf var ki A, türetilmiş sınıfları Bve Chem türetilmiş sınıfları yöntemi şeklinde bazı mantık paylaşan s(). Çoğaltmadan kurtulmak için doğru yaklaşıma karar vermek için, yöntemin s()genel arayüzün bir parçası olup olmadığını bilmek önemlidir .

Değilse (yöntemle kendi somut örneğinizde olduğu gibi b()), durum oldukça basittir. İşlemi gerçekleştirmek için gereken bağlamı da çıkararak ondan ayrı bir sınıf oluşturun. Bu, kalıtım üzerine kompozisyonun klasik bir örneğidir . Çok az içeriğe ihtiyaç duyulursa veya hiç bağlam yoksa, bazı yorumlarda önerildiği gibi basit bir yardımcı işlev zaten yeterli olabilir.

Eğer s()kamu arayüzünün bir parçası olan, biraz daha zor hale gelir. s()Hiçbir ilgisi olmayan Bve onunla Cilgili olmayan varsayımlar altında, Aiçine s()koymamalısınız A. Öyleyse nereye koymalı? Ben Itanımlayan ayrı bir arayüz beyan iddia s(). Sonra tekrar, ayrı uygulanması için mantığı içerir sınıf oluşturmak gerekir s()ve her iki Bve Cbuna bağlı.

Son, burada bir soyut sınıf için bir arayüz için gitmek ve ne zaman ne zaman karar vermek yardımcı olabilir böylece ilginç bir soru mükemmel bir cevap bağlantısıdır:

İyi bir soyut sınıf, işlevselliği veya durumu paylaşılabildiği için yeniden yazılması gereken kod miktarını azaltacaktır.


Genel arayüzün bir parçası olan s () şeyleri çok daha zor hale getirir. Genellikle aynı sınıftaki diğer genel yöntemleri çağıran genel yöntemleri tanımlamaktan kaçınırım.
Alexandros Gougousis

1

Bana öyle geliyor ki hem mantıksal hem de teknik olarak nesne yönelimi noktasını kaçırıyorsunuz. İki senaryoyu tanımlarsınız: bir temel sınıftaki türlerin ortak davranışlarını gruplandırma ve polimorfizm. Bunların her ikisi de soyut bir sınıfın meşru uygulamalarıdır. Ancak sınıfı soyut yapıp yapmamanız teknik olasılıklara değil, analitik modelinize bağlıdır.

Gerçek dünyada enkarnasyonu olmayan ancak var olan belirli türlerin zeminini oluşturan bir türü tanımalısınız. Örnek: bir hayvan. Hayvan diye bir şey yoktur. Her zaman bir köpek ya da kedi ya da her neyse, ama gerçek bir hayvan yok. Oysa Animal hepsini çerçeveliyor.

Sonra seviyelerden bahsediyorsun. Kalıtım söz konusu olduğunda, seviyeler anlaşmanın bir parçası değildir. Ortak verileri veya ortak davranışları da tanımıyor, bu muhtemelen yardımcı olmayacak teknik bir yaklaşımdır. Bir klişeyi tanımalı ve sonra temel sınıfı eklemelisiniz. Böyle bir stereotip yoksa, birden fazla sınıf tarafından uygulanan birkaç arabirimle daha iyi olabilirsiniz.

Soyut sınıfınızın üyeleri ile birlikte adı anlamlı olmalıdır. Hem teknik hem de mantıksal olarak türetilmiş herhangi bir sınıftan bağımsız olmalıdır. Hayvan gibi soyut bir yöntem olabilir yiyin (polimorfik olurdu) ve boolean soyut özellikleri IsPet ve IsLifestock, kediler veya köpekler veya domuzlar hakkında bilmeden mantıklı. Herhangi bir bağımlılık (teknik veya mantıksal) yalnızca bir yoldan gitmelidir: torundan tabana. Temel sınıfların kendileri de azalan sınıflar hakkında bilgi sahibi olmamalıdır.


Bahsettiğiniz stereotip, daha üst düzey işlevsellik hakkında konuştuğumda veya bir başkası hiyerarşide daha yüksek sınıf tarafından tanımlanmış genelleştirilmiş kavramlar hakkında konuştuğumda (yani daha düşük sınıflar tarafından tanımlanan daha özel kavramlar) hiyerarşi).
Alexandros Gougousis

"Temel sınıfların kendileri de azalan sınıflar hakkında hiçbir bilgiye sahip olmamalıdır." Buna tamamen katılıyorum. Bir ebeveyn sınıfı (soyut ya da değil), çocuk sınıfı uygulamasının mantıklı olması için denetlenmesi gerekmeyen bağımsız bir iş mantığı parçası içermelidir.
Alexandros Gougousis

Bir temel sınıf hakkında alt türlerini bilen başka bir şey daha var. Ne zaman yeni bir alt tip geliştirilirse, temel sınıfın değiştirilmesi gerekir, bu da geriye dönük ve açık-kapalı prensibini ihlal eder. Son zamanlarda mevcut kodda bu karşılaştım. Temel sınıf, uygulamanın yapılandırmasına bağlı olarak alt tür örnekleri oluşturan statik bir yönteme sahipti. Amaç bir fabrika modeliydi. Bu şekilde yapılması SRP'yi de ihlal eder. Bazı SOLID noktalarında "duh, bu açık" veya "dil otomatik olarak bunu halleder" diye düşünürdüm ama insanların hayal edebileceğimden daha yaratıcı olduğunu gördüm.
Martin Maat

0

İlk durumda , sorunuzdan:

Bu gibi durumlarda, soyut bir sınıf, bir taslak, işlevsellik hakkında yüksek düzeyde bir açıklama veya ona ne derseniz diye çalışır.

İkinci durum:

Diğer bazı zamanlarda ... soyut sınıf, genellikle, türetilmiş sınıfların içerdiği her türlü ortak kodu koyabileceğiniz bir yer olarak kullanılır.

Ardından, sorunuz :

Bu iki durumdan hangisinin kural olması gerektiğini merak ediyorum. ... Soyut sınıfın kendisi için bir anlamı olmayabilecek kod, sadece türetilmiş sınıflar için ortak olduğu için orada olmalı mıdır?

IMO'nun iki farklı senaryosu var, bu nedenle hangi tasarımın uygulanması gerektiğine karar vermek için tek bir kural çizemezsiniz.

İlk durumda, diğer cevaplarda daha önce bahsedilen Şablon Yöntemi modeli gibi şeyler var .

İkinci durumda, ab () yöntemi alan soyut sınıf A örneğini verdiniz; IMO b () yöntemi yalnızca TÜM diğer türetilmiş sınıflar için anlamlıysa taşınmalıdır. Başka bir deyişle, iki yerde kullanıldığından taşıyorsanız, muhtemelen bu iyi bir seçim değildir, çünkü yarın, b () 'nin hiçbir anlam ifade etmediği yeni bir beton Türetilmiş sınıf olabilir. Ayrıca, dediğin gibi, A sınıfına ayrı ayrı bakarsanız ve b () de bu bağlamda bir anlam ifade etmiyorsa, muhtemelen bunun iyi bir tasarım seçimi olmaması bir ipucu.


-1

Aynı soruları bir süre önce yaşadım!

Bu konuya rastladığımda geldiğim şey, bulamadığım gizli bir kavram olduğunu görüyorum. Ve bu kavram, muhtemelen bir değer nesnesi ile ifade edilmemelidir . Çok soyut bir örneğiniz var, bu yüzden korkarım kodunuzu kullanarak ne demek istediğimi gösteremiyorum. Ama işte kendi pratiğimden bir örnek. Dış kaynağa istek göndermek ve yanıtı ayrıştırmak için bir yol temsil eden iki sınıf vardı. Taleplerin nasıl oluştuğu ve cevapların nasıl ayrıştırıldığı konusunda benzerlikler vardı. Hiyerarşim şöyle görünüyordu:

abstract class AbstractProtocol
{
    /**
     * @return array Registration params to send
     */
    abstract protected function assembleRegistrationPart();

    /**
     * @return array Payment params to send
     */
    abstract protected function assemblePaymentPart();

    protected function doSend(array $data)
    {
        return
            (new HttpClient(
                [
                    'timeout' => 60,
                    'encoding' => 'utf-8',
                    'language' => 'en',
                ]
            ))
                ->send($data);
    }

    protected function log(array $data)
    {
        $header = 'Here is a request to external system!';
        $body = implode(', ', $this->maskData($data));
        Logger::log($header . '. \n ' . $body);
    }
}

class ClassicProtocol extends AbstractProtocol
{
    public function send()
    {
        $registration = $this->assembleRegistrationPart();
        $payment = $this->assemblePaymentPart();
        $specificParams = $this->assembleClassicSpecificPart();

        $dataToSend =
            array_merge(
                $registration, $payment, $specificParams
            );

        $this->log($dataToSend);

        $this->doSend($dataToSend);
    }

    protected function assembleRegistrationPart()
    {
        return ['hello' => 'there'];
    }

    protected function assemblePaymentPart()
    {
        return ['pay' => 'yes'];
    }
}

Böyle bir kod sadece miras kötüye olduğunu gösterir. Bu şekilde yeniden düzenlenebilir:

class ClassicProtocol
{
    private $request;
    private $logger;

    public function __construct(Request $request, Logger $logger, Client $client)
    {
        $this->request = $request;
        $this->client = $client;
        $this->logger = $logger;
    }

    public function send()
    {
        $this->logger->log($this->request->getData());
        $this->client->send($this->request->getData());
    }
}

$protocol =
    new ClassicProtocol(
        new PaymentRequest(
            new RegistrationData(),
            new PaymentData(),
            new ClassicSpecificData()
        ),
        new ClassicLogger(),
        new ClassicClient()
    );

class RegistrationData
{
    public function getData()
    {
        return ['hello' => 'there'];
    }
}

class PaymentData
{
    public function getData()
    {
        return ['pay' => 'yes'];
    }
}

class ClassicLogger
{
    public function log(array $data)
    {
        $header = 'Here is a request to external system!';
        $body = implode(', ', $this->maskData($data));
        Logger::log($header . '. \n ' . $body);
    }
}
class ClassicClient
{
    private $properties;

    public function __construct()
    {
        $this->properties =
            [
                'timeout' => 60,
                'encoding' => 'utf-8',
                'language' => 'en',
            ];
    }
}

O zamandan beri mirasa çok dikkatli davranıyorum çünkü birçok kez yaralandım.

O zamandan beri miras konusunda başka bir sonuca vardım. İç yapıya dayanan mirasa şiddetle karşıyım . Kapsüllemeyi kırar, kırılgandır, sonuçta prosedüreldir. Ben Ve doğru yolu Alanımı ayrıştırmak , kalıtım sadece çok sık olmaz.


@Downvoter, lütfen bana bir iyilik yapın ve bu cevapta neyin yanlış olduğunu yorumlayın.
Vadim Samokhin
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.