Noin kuukausi sitten kirjoitin yhdestä olioohjelmoinnin pilareista (erityisesti abstraktio). Viestissä määritin abstraktion seuraavasti:
Sen sijaan aiomme ottaa ideoita heidän luokkiinsa. Ja tässä on keskeinen ajatus: Luokan tulee edustaa substantiivia.
Ja vaikka se on edelleen totta, abstraktien luokkien idea on jotain erilaista olioohjelmoinnissa.
Kuulostaa hämmentävältä, eikö? Tuo on:
- yhdellä tasolla meillä on abstraktio, joka määritellään ajatukseksi, että otamme idean ja edustamme sitä luokassa,
- toisella tasolla meillä on abstrakteja luokkia, joita käytetään auttamaan määrittämään toimintoja, jotka alaluokkien on toteutettava.
Ja jos se ei ole tarpeeksi hämmentävää, yhdistämme tämän rajapintoihin, jotka tarjoavat sopimuksen toteutusluokkien on seurattava, ja sitten yhdistämme sen abstrakteihin luokkiin, jotka määrittelevät menetelmiä, jotka on myös toteutettava, mutta jotka voivat myös toteuttaa omia menetelmiään.
Oletko vielä hämmentynyt? Ei huolia. Seuraavien kolmen postauksen tarkoitus on tehdä seuraava:
- Määrittele mitä abstraktit luokat ovat,
- Kuvaile erilaisia abstrakteja luokkia ja käyttöliittymiä,
- Auta päättämään, milloin haluat käyttää toista.
Tässä on koko idea abstraktien luokkien takana.
Abstrahoiva käyttäytyminen
Ensinnäkin abstraktioissa ja abstrakteissa luokissa on ero. Edellinen viittaa ajatukseen esittää jotain ohjelmoinnissa; jälkimmäinen viittaa todelliseen tapaan kirjoittaa koodia.
Ja yksi parhaista tavoista, joita olen löytänyt abstraktien luokkien ajattelemiseen olioohjelmoinnissa, on ajatella niitä näin:
Abstraktit luokat korvaavat toteutuksen.
Ehkä toinen tapa ajatella niitä ovat paikkamerkit. Viime kädessä ne tarjoavat käyttäytymisen, joka alaluokkien on toteutettava.
Miten tämä eroaa käyttöliittymästä? Muista, että käyttöliittymä määrittelee funktiolle allekirjoituksen (funktion nimi, sen argumentit ja sen näkyvyysmuuntimet), joka luokan on toteutettava.
Abstraktio puolestaan korvaa toteutukselle, joka alaluokan on toteutettava. Mutta ehkä tämä näkyy parhaiten koodin avulla.
Abstraktio käytännössä
Oletetaan, että työskentelet projektin parissa ja huomaat, että sinulla on toimintoja, joita on useammassa kuin yhdessä paikassa. Sen lisäksi, että se rikkoo koko DRY-ideaa, se voi myös olla paikka, jossa voit abstrakti toiminnallisuuden perusluokkaan ja käyttää sitä uudelleen.
Tarkastellaanpa tätä julkaisujärjestelmän yhteydessä. WordPress ei välttämättä toteuta sitä tällä tavalla, mutta se käyttää meille tuttua ideaa: Taksonomiat.
Muista, että WordPressissä on tunnisteita ja luokkia. Näiden kahden välillä on hienoisia eroja (kuten hierarkkinen vai ei), mutta niillä on myös samanlaisia ominaisuuksia, kuten nimi ja etana.
Taksonomian abstraktio
Joten voimme aloittaa kirjoittamalla abstraktin taksonomialuokan, joka abstrahoi yhteisen toiminnallisuuden omaan luokkaansa.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
Yllä olevasta koodista näet, että olen tehnyt seuraavat:
- julisti luokan abstraktiksi
- määrittänyt useita attribuutteja, jotka asetetaan rakentajassa
- toteutti useita julkisia tehtäviä,
- lisätty useita suojattuja menetelmiä.
Keskeinen poikkeama tämän luokan tarkastelusta on, että kaikilla luokilla, jotka toteuttavat tämän abstraktin luokan, on automaattisesti konstruktorissa määritetyt toiminnot, getName -funktio ja getSlug- funktio.
Niillä ei kuitenkaan ole abstraktien funktioiden toteutusta. Ne jäävät alaluokkien toteuttamaan (jotka jaan hetken).
Konkreettinen taksonomia: tunniste
Nyt kun meillä on määritelty abstrakti luokka, on mahdollista todella toteuttaa abstraktio. Esimerkiksi :
<?php
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
Huomaa yllä olevassa koodissa, että luokka tekee vain toteutuksen abstraktissa luokassa määritellyille abstrakteille funktioille (joka on määritelty luokan määritelmän laajennusfunktiolla).
Myöhemmin tässä artikkelissa kerron, kuinka tätä koodia testataan, mutta huomaa, että yllä oleva ei tarjoa vain näkemäsi toiminnallisuutta, vaan myös taksonomia – luokan toimintoja.
Konkreettinen taksonomia: luokka
Ennen kuin tarkastelen tätä käytännössä, haluan myös määritellä luokan. Tämä sisältää koodin, joka toteuttaa abstraktin luokan funktioita, mutta myös omia toimintojaan.
<?php
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
Täällä meillä on kaikki Taksonomia -luokan mukana tuleva, mutta olemme myös määrittäneet omaisuutemme sen ylätunnisteelle sekä getter- ja setter-menetelmille. Vaikka se on tässä tapauksessa triviaalia, se osoittaa, kuinka hierarkkiset kategoriat voivat toimia.
Lisäksi, jos luokalla ei ole yläluokkaa, tunnukseksi asetetaan -1, mikä helpottaa kirjoittamista automaattista testausta varten tai jopa tarkistaa, onko sillä yläluokkaa.
Sen näkeminen toiminnassa
Koko koodin esittelyä varten minulla on sisältö, joka sisältää kaiken koodin yhdessä tiedostossa. Parhaana käytäntönä en suosittele tätä. Sen sijaan jokainen luokka tulisi säilyttää omassa tiedostossaan ja jokaisen luokan tulee kuulua nimiavaruuteen.
Mutta koska tämä on puhtaasti esittelytarkoituksessa, se riittää.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
/*--*/
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
/*--*/
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
/*- Tag Demo ----------------------------*/
$tag = new Tag('Acme Tag');
echo $tag->getName();
echo $tag->getSlug();
/*- Category Demo -----------------------*/
$category = new Category('Acme Category');
echo $category->getName();
echo $category->getSlug();
echo $category->getParentId();
$category->setParentId(100);
echo $category->getparentId();
Kun suoritat tämän konsolissa, sinun pitäisi nähdä seuraavanlainen tulos:
Saatat joutua lisäämään muutamia kaikulauseita varmistaaksesi, että se luo uusia rivejä, mutta se on sinun.
Entä käyttöliittymät?
Eli tässä vaiheessa:
- meillä on toimiva määritelmä siitä, mitä abstraktit luokat ovat,
- meillä on esimerkki siitä, miltä abstraktit luokat näyttävät,
- ja meillä on toimiva demo niiden suorituskyvystä.
Seuraavaksi sukeltan syvemmälle abstraktien luokkien ja käyttöliittymien välisten erojen käsittelyyn, milloin saatat haluta käyttää toista päällekkäin tai milloin haluat käyttää niitä yhdessä toistensa kanssa.