it-swarm-eu.dev

Měl bych se stále řídit „programováním na rozhraní ne implementace“, i když si myslím, že použití konkrétních členů třídy je jednodušší řešení?

Podle Pochopení "programování do rozhraní" , jak chápu, myslím, že bych měl záviset pouze na abstraktní třídě. V některých případech však například Student:

public class Student {
    private String name;
    private int age;
}

upravit ji tak, aby se stala závislou pouze na abstraktní třídě (která MyIString může být nová abstraktní třída, která obtéká String):

public class Student {
    private MyIString name;
    private Java.lang.Number age;
}

Myslím, že modifikovaný je složitější. A ještě „skutečnější“ příklad řekněte Address:

public class Address {
    private ZipCode zipcode;
}

které potřebuji pouze pro jeden typ ZipCode, ale pokud jej upravím jako:

public class Address {
    private IZipCode zipcode;
}

které IZipCode je rozhraní, pak si myslím, že by mohlo uvést ostatní spoluhráče v omyl, že by existovaly jiné typy ZipCodes.

Myslím, že výše uvedené případy jsou složitější a méně udržovatelné, pokud mi bylo umožněno, aby třída používala pouze abstraktní členy třídy. Moje otázka tedy zní, mám se stále řídit „programováním na rozhraní, které není implementací“, pokud se „sledované“ stane složitějším (podle mého názoru)?

34
aacceeggiikk

Programování na rozhraní znamená, že byste se měli zaměřit na to, co kód dělá, nikoli jak je skutečně implementováno. Viz odpověď Telastyna na pochopení „programování do rozhraní“ . Třídy rozhraní pomáhají prosazovat tento pokyn, ale to neznamená, že byste nikdy neměli používat konkrétní třídy.

Líbí se mi například příklad PSČ:

V roce 1963 zavádí americká poštovní služba PSČ, které se skládá z pěti číslic. Nyní můžete získat (špatnou) představu, že celé číslo je dostatečnou reprezentací a použít jej v celém kódu. 1983 je představen nový formát PSČ. Používá 5 číslic, spojovník a 4 další číslice. Najednou již PSČ již nejsou celá čísla a je třeba změnit všechna místa, která používají celá čísla pro PSČ, na něco jiného, ​​například na řetězce. Tomuto refaktoringu bylo možné zabránit, pokud by byla použita označená třída ZipCode. Poté bylo třeba změnit pouze vnitřní reprezentaci třídy ZipCode a její použití by zůstala stejná.

Použití třídy ZipCode je již programování na rozhraní : Místo programování proti nějaké konkrétní implementaci ("integer"/"string"), programujete proti nějaké abstrakci ("PSČ").

V případě potřeby můžete přidat další úroveň abstrakce: Možná budete muset podporovat různé formáty poštovních směrovacích čísel pro různé země. Například byste mohli přidat rozhraní IPostalCode s implementacemi jako PostalCodeUS (což je pouze výše uvedená ZipCode), PostalCodeIreland, PostalCodeNetherlands atd. Nicméně příliš mnoho úrovní abstrakce může kód také složitější a obtížnější uvažovat a myslím, že záleží na požadavcích vaší aplikace, zda chcete použít nějakou třídu ZipCode nebo nějakou IPostalCode rozhraní. Možná ZipCode, který interně používá (unicode) řetězec, je dost dobrý pro všechny země a věci specifické pro danou zemi, například validace, mohou být zpracovány mimo třídu v některých PostalCodeValidator/IPostalCodeValidator.

76
pschill

„Programování do rozhraní“ nevyžaduje jazykové klíčové slovo interface. To znamená, že vám záleží na tom, co slibuje daný typ chování.

Je vám jedno jakJava.lang.String dělá všechny věci, které dělá, pouze že můžete psát

name = "aacceeggiikk";
age = 42;

„Programování k implementaci“ by pomocí reflexe získalo u soukromých členů String a hádalo se s nimi. A pak jste naštvaní, že aktualizace porušila váš kód, protože jste se spoléhali na něco, co se změnilo.

47
Caleth

Je to pouze vodítko

Stejně jako koncept jako KISS je vodítko.

Pokud budete postupovat podle pokynů „naprogramovat rozhraní, ne implementaci“, porušujete zásadu KISS). Ale vytvořením partie jednoduchých tříd, které opakují nějaký kód nebo akce, porušujete zásadu DRY .

Neexistuje žádný způsob, jak se tam držet všech pokynů, principů nebo „pravidel“. Musíte zjistit, jaká rovnováha má smysl pro aplikaci vaše.

Moje vlastní zkušenost spočívá v tom, že snaha o to, aby vše bylo podnikově zajištěno pomocí abstrahovaných vrstev na abstrahovaných vrstvách, je často nadměrná a pro mnoho aplikací zbytečně složitá.

10
Ivo Coumans
  1. Programování na rozhraní se týká objektů, entit, které mohou mít také primitivní vlastnosti jako boolean, int, BigDecimal a String. Na této úrovni již neplatí programování na rozhraní .

  2. Existuje však další princip, který je založen na zkušenosti, že se používají primitivní typy tam, kde by měl existovat obal pro abstraktnější třídu hodnot. Takže místo toho

    String telephone;
    

    můžete raději mít hodnotu třídy Telefon:

    Telephone telephone.
    

    To se může postarat o číslo země, kanonickou podobu, rovnost a podobně. Telefon by mohl být opět rozhraním a mohla by existovat implementační třída TelephoneImpl, ale já - naštěstí - jen zřídka to vidím.

    Ještě lepší je, když více než jedno primitivní pole může být nahrazeno abstraktnější třídou, například FullName, řekněme int similarityPercentage(FullName other).

  3. Možná by se mělo zmínit, že interní objekty s interními implementacemi mohou využívat rozhraní. List má rozhraní Iterator iterator() implementující místní objekt s přístupem do kontejneru seznamu. Zde je však rozhraní součástí řešení, nikoli stylem kódování.

Zjednodušeně:

Programování pomocí rozhraní odděluje implementační třídu, umožňuje několik implementací, má jedinou odpovědnost jako API. Týká se chování, metod. Vezměte JDBC jako příklad.

Pole jsou konkrétní vlastnosti. Jsou to data . Měli by být také abstrahováni a skrývat implementaci pomocí názvu třídy a poskytovat přístupové metody pro správné použití. Nicméně to je jiná úroveň. Vezměte třídy LocalDate, LocalDateTime, YearMonth místo starého Datum pro všechny.

7
Joop Eggen

Chcete-li citovat můj kolega - " Můžete strávit hodiny navrhováním auta na počítači, ale v určitém okamžiku se kola musí skutečně dotknout země.".

tj. ne všechno může/mělo by být odebráno, v určitém okamžiku potřebujete konkrétní typ.

Pro vaši třídu s poštovním směrovacím číslem by skutečně měla být otázka, co je PSČ (z hlediska vaší aplikace)?

Answer - jedná se o řetězec, takže vlastnost by měla být řetězec.

public class Address {
     public String ZipCode...
}

P.S. nezačněte se zabývat abstrahováním základních jazykových typů - tímto způsobem leží šílenství!

Existuje řada odpovědí/komentářů, které komentují rozmanitost PSČ/poštovních směrovacích čísel v různých zemích, a to je pravda, ale všechny jsou nakonec řetězce.

Doufejme, že tato třída adresy nezajímá, co je v PSČ (jen to, že jde o řetězec) a existuje další třída, kterou používáte k ověření vaší adresy (a že je možné vypnout implementaci - - to třída může být užitečná).

Takže program na rozhraní, kde to má smysl, a zejména tam, kde může být nutné změnit implementaci nebo vložit chování. Nepoužívejte rozhraní pro všechno!

6
Paddy

V příkladu adresy/PSČ:

public class Address {
private ZipCode zipcode;
}

Vázání na konkrétní implementaci namísto rozhraní ponechává 3 problémy:

  • A - internacionalizace - např. Kanadský poštovní kód je něco jako V1C3X8, adresy ostatních zemí se opět liší. Hodně štěstí při ukládání fakturačních adres zákazníka v globalizované ekonomice.
  • B - Změna v implementaci, a proto by mělo být jako rozhraní spotřebováno vše - Jednoho dne byste se mohli rozhodnout, že je efektivnější ukládat adresy jako GeoCode s třemi slovy uložený jako číslo a podlaha./číslo jednotky pro vícepodlažní budovu. Adresa by tedy mohla být změněna z něčeho podobného:
public class Address : IAddress 
{
    public IZipCode ZipCode{get; private set;}
    public string StreetName{get;private set}
}

Něco jako:

public class Address : IAddress 
{
    public Address(IGeoCodeService geoCodeService/*Dependency injected*/){........}

    private long What3WordsGeoCodeAsLong {get; set;}

    public IZipCode ZipCode => GetZipCodeFromGeoCode();//calls out to a service to 
    public string StreetName => GetStreetNameFromGeoCode();
}

Po serializaci je druhá implementace mnohem menší k uložení a je imunní vůči zastaralosti, pokud se některá země rozhodne přesunout oblasti poštovního směrování, což se stává častěji, než si myslíte (s kompromisem) nutnosti použít službu k vyhledání poštovního kódu pokaždé.), zatímco cokoli, co používá IAddress, si ani nebude „vědomo“, že implementace je nyní úplně jiná!

  • C - Testování jednotky, vidím, že používáte konvenci názvů .Net. Nejsem si vědoma žádného. Net framework pro testování, který vám umožní posmívat metody na adrese a ZipCode ve všech třídách, kde se používají, musíte je konzumovat jako IAddress a IZipcode.
0
Eugene

To je můj názor na „programování do rozhraní“. Existují dvě části: vstupy a výstupy.

Vstupy

Rozlišuji mezi objekty, které jsou data, a objekty, které mají chování, zejména vedlejší účinky.

V obou vašich příkladech bych použil konkrétní třídy: řetězce a PSČ jsou data, IMO. (NEZNAMENAL bych jen ZipCode na String, protože jedna z navrhovaných odpovědí !!! Nemůžete jen předat žádný starý řetězec a chovat se k němu jako k ZipCode. Vaše třída ZipCode by měla mít různé konstruktory pro různé druhy kódů Zip, které byste mohli mít vypořádat se. Interně můžete uložit řetězec, ale alespoň provést ověření v konstruktorech!).

Kdybych však potřeboval objekt, který by „nějak“ pracoval na počítači (nějaký vágní termín), použil bych rozhraní. Pokud například potřebuji objekt, který mi umožní hledat studenty podle jména, vezmu rozhraní pro vstup. Rozhraní by bylo něco jako:

interface StudentSearchInterface {
    Optional<Student> search(String query);
}

Důvodem je dobrý nápad, protože poskytovatelé si můžete později vyměnit a také vám umožní psát mnohem lepší testy, kde můžete kódovat "falešné" objekty StudentSearchInterface, které vracejí přesně to, co chcete pro váš konkrétní test. Může být obtížné otestovat produkční třídu, která implementuje StudentSearchInterface: co když dotazuje server na internetu? Nebo čte z databáze? Výsledky se mohou mezi hovory změnit na search(), takže to opravdu nemůžete vyzkoušet.

Výstupy

Vaše vrácené hodnoty by měly být buď „data“, nebo také rozhraní. Podle dat mám na mysli třídu, která má opravdu jen getters a/nebo setters a možná nějaké triviální metody jako toString() atd. Jinak udělejte rozhraní. Obvyklým příkladem je vrácení List<Foo> Namísto ArrayList<Foo>. To vám dává svobodu změnit logiku uvnitř vaší metody a použít něco jiného, ​​co implementuje List místo toho, aby se zaseklo s konkrétním implementátorem List, protože to je to, co jste použili jako první.

Stejně jako označujeme některé metody a pole jako private, abychom je zapouzdřili, měli bychom používat rozhraní pouze k poskytování informací na základě „potřeby vědět“.

0
R. Agnese