✅ WEB- och WordPress -nyheter, teman, plugins. Här delar vi tips och bästa webbplatslösningar.

Abstrakta klasser, del 1 – Abstraherande beteende

29

För ungefär en månad sedan skrev jag om en av pelarna i objektorienterad programmering (särskilt abstraktion). I inlägget definierade jag abstraktion som följande:

Istället kommer vi att abstrahera idéer i deras klasser. Och det finns en nyckelidé här: En klass ska representera ett substantiv.

Och även om det fortfarande är sant, är idén med abstrakta klasser något som är annorlunda i objektorienterad programmering.

Det låter förvirrande, eller hur? Det är:

  • på en nivå har vi abstraktion definierad som idén att vi tar en idé och representerar den i en klass,
  • på en annan nivå har vi abstrakta klasser som används för att hjälpa till att definiera funktioner som underklasser måste implementera.

Och om det inte är tillräckligt förvirrande, blandar vi detta med gränssnitt som ger ett kontrakt som implementerande klasser måste följa, och sedan blandar vi det med abstrakta klasser som definierar metoder som också måste implementeras men som också kan implementera egna metoder.

Förvirrad ännu? Inga problem. Hela poängen med de kommande tre inläggen är att göra följande:

  1. Definiera vad abstrakta klasser är,
  2. Beskriv de olika i abstrakta klasser och gränssnitt,
  3. Hjälp att bestämma när du vill använda den ena framför den andra.

Med det sagt, här är hela idén bakom abstrakta klasser.

Abstraherande beteende

Först och främst finns det en skillnad i abstraktionsklasser och abstrakta klasser. Den förra hänvisar till idén att representera något i programmering; det senare hänvisar till ett faktiskt sätt att skriva kod.

Och ett av de bästa sätten jag har hittat på vad jag tycker om abstrakta klasser i objektorienterad programmering är att tänka på dem så här:

Abstrakta klasser är ett substitut för implementering.

Ett annat sätt att tänka på dem är kanske som platshållare. I slutändan ger de det beteende som underklasserna måste implementera.

Hur skiljer sig detta från ett gränssnitt? Kom ihåg att ett gränssnitt definierar en signatur för en funktion (funktionsnamnet, dess argument och dess synlighetsmodifierare) som en klass måste implementera.

Abstraktion, å andra sidan, ger ett substitut för implementering som en underklass måste implementera. Men kanske visas detta bäst genom att använda kod.

Abstraktion i praktiken

Låt oss säga att du arbetar med ett projekt och du upptäcker att du har funktionalitet som finns på mer än ett ställe. Förutom att bryta mot hela idén med DRY, har det också potential att vara en plats där du kan abstrahera funktionalitet till en basklass och använda den igen.

Låt oss överväga detta i samband med ett publiceringssystem. Det är inte nödvändigtvis hur WordPress implementerar det, men det använder en idé som vi är bekanta med: taxonomier.

Kom ihåg att vi har taggar och kategorier i WordPress. Det finns subtila skillnader mellan de två (som om en är hierarkisk eller inte), men de delar också liknande attribut som att ha ett namn och en snigel.

En taxonomiabstraktion

Så vi kan börja med att skriva en abstrakt Taxonomy-klass som abstraherar den gemensamma funktionaliteten till sin egen klass.

<?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();

}

I koden ovan ser du att jag har gjort följande:

  • förklarade klassen abstrakt
  • definierat flera attribut som kommer att ställas in i konstruktorn
  • tillhandahållit flera offentliga funktioner med implementering,
  • lagt till flera skyddade metoder.

Det viktigaste med att titta på den här klassen är att varje klass som implementerar den här abstrakta klassen automatiskt kommer att ha den funktionalitet som definieras i konstruktorn, funktionen getName och funktionen getSlug.

De kommer dock inte att ha implementeringen av de abstrakta funktionerna. Det är det som återstår att implementera av underklasserna (som jag kommer att dela med mig av ett ögonblick).

En konkret taxonomi: en tagg

Nu när vi har en abstrakt klass definierad är det möjligt att faktiskt implementera abstraktionen. Till exempel :

<?php

class Tag extends Taxonomy
{
  protected function isHierarchical() {
    return false;
  }

  protected function isCategory() {
    return $this->isHierarchical;
  }

  protected function isTag() {
    return !$this->isHierarchical;
  }
}

I koden ovan, lägg märke till att allt klassen gör är att tillhandahålla implementeringen av de abstrakta funktionerna som definieras i abstraktklassen (vilket specificeras av funktionen extends i klassdefinitionen).

Senare i den här artikeln kommer jag att dela hur man testar den här koden men notera att ovanstående inte bara erbjuder den funktionalitet som du ser utan också funktionaliteten för klassen Taxonomy.

En konkret taxonomi: en kategori

Innan jag tar en titt på detta i praktiken vill jag definiera en kategori också. Detta kommer att inkludera kod som implementerar funktioner från den abstrakta klassen men också egna funktioner.

Se nedan:

<?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;
  }
}

Här har vi allt som följer med klassen Taxonomy, men vi har också definierat vår egenskap för dess föräldra-ID och getter- och settermetoder. Även om det är trivialt i det här fallet, visar det hur kategorier, som är hierarkiska, kan fungera.

Vidare, om kategorin inte har någon förälder, ställs ID:t till -1 vilket gör det enkelt att skriva för automatisk testning eller till och med kontrollera om den har en förälder.

Att se det i aktion

För att demo hela den här koden har jag en sammanfattning som inkluderar all kod i en enda fil. Som en bästa praxis rekommenderar jag inte detta. Istället bör varje klass hållas i sin egen fil, och varje klass bör tillhöra ett namnområde.

Men eftersom detta är enbart i demonstrationssyfte räcker det.

<?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();

När du kör detta i konsolen bör du se något i stil med följande utdata:

Du kan behöva lägga till några ekosatser för att se till att det skapar nya linjer, men det är upp till dig.

Hur är det med gränssnitt?

Så, vid det här laget:

  • vi har en fungerande definition av vad abstrakta klasser är,
  • vi har ett exempel på hur abstrakta klasser ser ut,
  • och vi har en fungerande demo av hur de kan prestera.

Därefter ska jag ta en djupare dykning i att diskutera skillnaderna mellan abstrakta klasser och gränssnitt, när du kanske vill använda den ena framför den andra, eller när du kanske vill använda dem i kombination med varandra.

Inspelningskälla: tommcfarlin.com

Denna webbplats använder cookies för att förbättra din upplevelse. Vi antar att du är ok med detta, men du kan välja bort det om du vill. Jag accepterar Fler detaljer