it-swarm.com.de

Was ist der Unterschied zwischen Abstraktion und Verallgemeinerung?

Ich verstehe, dass es bei Abstraktion darum geht, etwas konkreter zu nehmen und abstrakter zu machen. Dies kann entweder eine Datenstruktur oder eine Prozedur sein. Zum Beispiel:

  1. Datenabstraktion: Ein Rechteck ist eine Abstraktion eines Quadrats. Es konzentriert sich auf die Tatsache, dass ein Quadrat zwei Paare von gegenüberliegenden Seiten hat, und ignoriert die Tatsache, dass benachbarte Seiten eines Quadrats gleich sind.
  2. Prozedural Abstraktion: Die Funktion map höherer Ordnung ist eine Abstraktion einer Prozedur, die einige Operationen an einer Liste von Werten ausführt, um eine völlig neue Liste von Werten zu erzeugen. Es konzentriert sich auf die Tatsache, dass die Prozedur jedes Element der Liste durchläuft, um eine neue Liste zu erstellen, und die tatsächlichen Vorgänge ignoriert, die für jedes Element der Liste ausgeführt werden.

Meine Frage ist also: Wie unterscheidet sich die Abstraktion von der Verallgemeinerung? Ich suche nach Antworten, die sich hauptsächlich auf die funktionale Programmierung beziehen. Wenn es jedoch Parallelen in der objektorientierten Programmierung gibt, möchte ich auch darüber erfahren.

35
Aadit M Shah

Objekt:

portal cake photo

Abstraktion:

enter image description here

Verallgemeinerung:

many desserts

Beispiel in Haskell:

Die Implementierung der Auswahl erfolgt mithilfe von Prioritätswarteschlange mit drei verschiedenen Schnittstellen:

  • eine offene Schnittstelle, wobei die Warteschlange als sortierte Liste implementiert wird,
  • eine abstrahierte Schnittstelle (so dass die Details hinter der Abstraktionsebene verborgen sind),
  • eine verallgemeinerte Schnittstelle (die Details sind noch sichtbar, die Implementierung ist jedoch flexibler).
{-# LANGUAGE RankNTypes #-}

module Main where

import qualified Data.List as List
import qualified Data.Set as Set

{- TYPES: -}

-- PQ new Push pop
-- by intention there is no build-in way to tell if the queue is empty
data PriorityQueue q t = PQ (q t) (t -> q t -> q t) (q t -> (t, q t))
-- there is a concrete way for a particular queue, e.g. List.null
type ListPriorityQueue t = PriorityQueue [] t
-- but there is no method in the abstract setting
newtype AbstractPriorityQueue q = APQ (forall t. Ord t => PriorityQueue q t)


{- SOLUTIONS: -}

-- the basic version
list_selection_sort :: ListPriorityQueue t -> [t] -> [t]
list_selection_sort (PQ new Push pop) list = List.unfoldr mypop (List.foldr Push new list)
  where
    mypop [] = Nothing -- this is possible because we know that the queue is represented by a list
    mypop ls = Just (pop ls)


-- here we abstract the queue, so we need to keep the queue size ourselves
abstract_selection_sort :: Ord t => AbstractPriorityQueue q -> [t] -> [t]
abstract_selection_sort (APQ (PQ new Push pop)) list = List.unfoldr mypop (List.foldr mypush (0,new) list)
  where
    mypush t (n, q) = (n+1, Push t q)
    mypop (0, q) = Nothing
    mypop (n, q) = let (t, q') = pop q in Just (t, (n-1, q'))


-- here we generalize the first solution to all the queues that allow checking if the queue is empty
class EmptyCheckable q where
  is_empty :: q -> Bool

generalized_selection_sort :: EmptyCheckable (q t) => PriorityQueue q t -> [t] -> [t]
generalized_selection_sort (PQ new Push pop) list = List.unfoldr mypop (List.foldr Push new list)
  where
    mypop q | is_empty q = Nothing
    mypop q | otherwise  = Just (pop q)


{- EXAMPLES: -}

-- priority queue based on lists
priority_queue_1 :: Ord t => ListPriorityQueue t
priority_queue_1 = PQ [] List.insert (\ls -> (head ls, tail ls))
instance EmptyCheckable [t] where
  is_empty = List.null

-- priority queue based on sets
priority_queue_2 :: Ord t => PriorityQueue Set.Set t
priority_queue_2 = PQ Set.empty Set.insert Set.deleteFindMin
instance EmptyCheckable (Set.Set t) where
  is_empty = Set.null

-- an arbitrary type and a queue specially designed for it
data ABC = A | B | C deriving (Eq, Ord, Show)

-- priority queue based on counting
data PQ3 t = PQ3 Integer Integer Integer
priority_queue_3 :: PriorityQueue PQ3 ABC
priority_queue_3 = PQ new Push pop
  where
    new = (PQ3 0 0 0)
    Push A (PQ3 a b c) = (PQ3 (a+1) b c)
    Push B (PQ3 a b c) = (PQ3 a (b+1) c)
    Push C (PQ3 a b c) = (PQ3 a b (c+1))
    pop (PQ3 0 0 0) = undefined
    pop (PQ3 0 0 c) = (C, (PQ3 0 0 (c-1)))
    pop (PQ3 0 b c) = (B, (PQ3 0 (b-1) c))
    pop (PQ3 a b c) = (A, (PQ3 (a-1) b c))

instance EmptyCheckable (PQ3 t) where
  is_empty (PQ3 0 0 0) = True
  is_empty _ = False


{- MAIN: -}

main :: IO ()
main = do
  print $ list_selection_sort priority_queue_1 [2, 3, 1]
  -- print $ list_selection_sort priority_queue_2 [2, 3, 1] -- fail
  -- print $ list_selection_sort priority_queue_3 [B, C, A] -- fail
  print $ abstract_selection_sort (APQ priority_queue_1) [B, C, A] -- APQ hides the queue 
  print $ abstract_selection_sort (APQ priority_queue_2) [B, C, A] -- behind the layer of abstraction
  -- print $ abstract_selection_sort (APQ priority_queue_3) [B, C, A] -- fail
  print $ generalized_selection_sort priority_queue_1 [2, 3, 1]
  print $ generalized_selection_sort priority_queue_2 [B, C, A]
  print $ generalized_selection_sort priority_queue_3 [B, C, A]-- power of generalization

  -- fail
  -- print $ let f q = (list_selection_sort q [2,3,1], list_selection_sort q [B,C,A])
  --         in f priority_queue_1

  -- power of abstraction (rank-n-types actually, but never mind)
  print $ let f q = (abstract_selection_sort q [2,3,1], abstract_selection_sort q [B,C,A]) 
          in f (APQ priority_queue_1)

  -- fail
  -- print $ let f q = (generalized_selection_sort q [2,3,1], generalized_selection_sort q [B,C,A])
  --         in f priority_queue_1

Der Code ist auch über Pastebin verfügbar.

Bemerkenswert sind die existentiellen Typen. Wie @lukstafi bereits betont hat, ist die Abstraktion dem existentiellen Quantifizierer ähnlich und die Generalisierung dem universellen Quantifizierer. __ Beachten Sie, dass zwischen der Tatsache, dass ∀xP (x) ∃xP (x) (in ein nicht leeres Universum), und dass es selten eine Verallgemeinerung ohne Abstraktion gibt (selbst C++ - ähnliche überladene Funktionen bilden in gewissem Sinne eine Art Abstraktion).

Credits: Portalkuchen von Solo . Desserttisch von djttwo . Das Symbol basierte auf dem Portal Artwork.

26
dtldarek

Eine sehr interessante Frage. Ich habe diesen Artikel zu dem Thema gefunden, in dem es genau heißt:

Während die Abstraktion die Komplexität reduziert, indem irrelevante Details ausgeblendet werden, reduziert die Generalisierung die Komplexität, indem mehrere Entitäten, die ähnliche Funktionen ausführen, durch ein einziges Konstrukt ersetzt werden.

Nehmen wir das alte Beispiel eines Systems, das Bücher für eine Bibliothek verwaltet. Ein Buch hat jede Menge Eigenschaften (Anzahl der Seiten, Gewicht, Schriftgröße (n), Umschlag, ...), aber für den Zweck unserer Bibliothek brauchen wir möglicherweise nur

Book(title, ISBN, borrowed)

Wir haben gerade von den echten Büchern in unserer Bibliothek abstrahiert und nur die Eigenschaften übernommen, die uns im Kontext unserer Anwendung interessierten.


Bei der Verallgemeinerung hingegen wird nicht versucht, Details zu entfernen, sondern die Funktionalität auf einen größeren (allgemeineren) Bereich von Elementen anzuwenden. Generische Container sind ein sehr gutes Beispiel für diese Denkweise: Sie möchten keine Implementierung von StringList, IntList usw. schreiben, weshalb Sie lieber ein generisch Liste, die für alle Typen gilt (wie List[T] in Scala). Beachten Sie, dass Sie nicht abstrahiert ​​die Liste haben, weil Sie keine Details oder Operationen entfernt haben, Sie haben sie nur allgemein auf alle Ihre Typen anwendbar gemacht.

Runde 2

@ dtldareks Antwort ist wirklich eine sehr gute Illustration! Hier ist ein Code, der weitere Erläuterungen liefern könnte.

Erinnern Sie sich an das Book, das ich erwähnt habe? Natürlich gibt es in einer Bibliothek noch andere Dinge, die man ausleihen kann (ich werde die Menge all dieser Objekte Borrowable nennen, obwohl das wahrscheinlich nicht einmal ein Wort ist: D):

Alle diese Elemente haben eine abstrakte Darstellung in unserer Datenbank und Geschäftslogik, wahrscheinlich ähnlich der unseres Book. Zusätzlich können wir ein Merkmal definieren, das allen Borrowables gemeinsam ist:

trait Borrowable {
    def itemId:Long
}

Wir könnten dann eine verallgemeinerte Logik schreiben, die für alle Borrowable gilt (an diesem Punkt ist es uns egal, ob es sich um ein Buch oder eine Zeitschrift handelt) :

object Library {
    def lend(b:Borrowable, c:Customer):Receipt = ...
    [...]
}

Zusammenfassend: Wir haben eine abstrakte Darstellung aller Bücher, Zeitschriften und DVDs in unserer Datenbank gespeichert, da eine exakte Darstellung weder machbar noch notwendig ist. Wir gingen dann weiter und sagten

Es ist egal, ob ein Buch, eine Zeitschrift oder eine DVD von einem Kunden ausgeliehen wird. Es ist immer der gleiche Prozess.

Wir verallgemeinerten den Vorgang des Ausleihens eines Gegenstandes, indem wir alle Dinge, die man ausleihen kann, als Borrowables definieren.

31
fresskoma

Ich werde einige Beispiele verwenden, um Verallgemeinerung und Abstraktion zu beschreiben, und ich werde auf diesen Artikel Bezug nehmen.

Meines Wissens gibt es keine offizielle Quelle für die Definition von Abstraktion und Verallgemeinerung im Programmierbereich (Wikipedia ist meiner Meinung nach wahrscheinlich am nächsten an einer offiziellen Definition), daher habe ich stattdessen einen Artikel verwendet, den ich für richtig halte glaubwürdig.

Generalisierung

In dem Artikel heißt es:

"Das Konzept der Verallgemeinerung in OOP bedeutet, dass ein Objekt den allgemeinen Zustand und das allgemeine Verhalten einer Klasse von Objekten einkapselt."

Wenn Sie zum Beispiel Generalisierung auf Formen anwenden, sind die allgemeinen Eigenschaften für alle Formtypen Fläche und Umfang.

Daher können eine verallgemeinerte Form (z. B. Shape) und Spezialisierungen davon (z. B. ein Kreis) in Klassen wie folgt dargestellt werden (beachten Sie, dass dieses Bild dem oben genannten Artikel entnommen wurde).

enter image description here

Wenn Sie in der Domäne von Düsenflugzeugen arbeiten, könnten Sie einen Jet als Verallgemeinerung haben, der eine Spannweite besitzt. Eine Spezialisierung eines Jets könnte ein FighterJet sein, der die Spannweiteigenschaft erben würde und seine eigene Eigenschaft haben würde, die für Kampfjets einzigartig ist, z. NumberOfMissiles.

Abstraktion

Der Artikel definiert Abstraktion als:

"das Verfahren zum Identifizieren gemeinsamer Muster mit systematischen Variationen; eine Abstraktion stellt das gemeinsame Muster dar und stellt ein Mittel zur Verfügung, um anzugeben, welche Variation verwendet werden soll (Richard Gabriel)"

Im Bereich der Programmierung:

Eine abstrakte Klasse ist eine übergeordnete Klasse, die die Vererbung zulässt. niemals instanziiert werden.

Daher ist eine Form in dem oben im Abschnitt "Generalisierung" genannten Beispiel abstrakt:

In der realen Welt berechnen Sie niemals die Fläche oder den Umfang eines generische Form, Sie müssen wissen, welche Art von geometrischer Form Sie haben weil jede Form (z. B. Quadrat, Kreis, Rechteck usw.) eine eigene .__ hat. Flächen- und Umfangsformeln.

Abgesehen davon, dass es sich bei abstrakt um eine Form handelt, ist auch eine Verallgemeinerung (da sie "allgemeinen Zustand und Verhalten für eine Kategorie von Objekten einkapselt", wobei in diesem Fall die Objekte Formen sind).

Um auf das Beispiel zurückzukommen, das ich über Jets und FighterJets gegeben habe, ist ein Jet nicht abstrakt, da eine konkrete Instanz eines Jet möglich ist, da es in der realen Welt existieren kann eine Instanz einer Form halten, z ein Würfel. Im Flugzeugbeispiel ist ein Jet also nicht abstrakt, sondern eine Verallgemeinerung, da es möglich ist, eine "konkrete" Instanz eines Jet zu haben. 

4
Ben Smith

Keine glaubwürdige/offizielle Quelle: ein Beispiel in Scala

"Abstraktion" haben

  trait AbstractContainer[E] { val value: E }

  object StringContainer extends AbstractContainer[String] {
    val value: String = "Unflexible"
  }

  class IntContainer(val value: Int = 6) extends AbstractContainer[Int]

  val stringContainer = new AbstractContainer[String] {
    val value = "Any string"
  }

und "Generalisierung"

  def specialized(c: StringContainer.type) =
    println("It's a StringContainer: " + c.value)

  def slightlyGeneralized(s: AbstractContainer[String]) =
    println("It's a String container: " + s.value)

  import scala.reflect.{ classTag, ClassTag }
  def generalized[E: ClassTag](a: AbstractContainer[E]) =
    println(s"It's a ${classTag[E].toString()} container: ${a.value}")

  import scala.language.reflectiveCalls
  def evenMoreGeneral(d: { def detail: Any }) =
    println("It's something detailed: " + d.detail)

ausführen

  specialized(StringContainer)
  slightlyGeneralized(stringContainer)
  generalized(new IntContainer(12))
  evenMoreGeneral(new { val detail = 3.141 })

führt zu

It's a StringContainer: Unflexible
It's a String container: Any string
It's a Int container: 12
It's something detailed: 3.141
2
CaringDev

Ich möchte eine Antwort für das größtmögliche Publikum anbieten, daher verwende ich die Lingua Franca des Webs, Javascript.

Beginnen wir mit einem gewöhnlichen imperativen Code:

// some data

const xs = [1,2,3];

// ugly global state

const acc = [];

// apply the algorithm to the data

for (let i = 0; i < xs.length; i++) {
  acc[i] = xs[i] * xs[i];
}

console.log(acc); // yields [1, 4, 9]

Im nächsten Schritt stelle ich die wichtigste Abstraktion in der Programmierung vor - Funktionen. Funktionen abstrakt über Ausdrücken:

// API

const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x]; // weird square function to keep the example simple

// some data

const xs = [1,2,3];

// applying

console.log(
  foldr(x => acc => concat(sqr_(x)) (acc)) ([]) (xs) // [1, 4, 9]
)

Wie Sie sehen, werden viele Implementierungsdetails abstrahiert. Abstraktion bedeutet die Unterdrückung von Details.

Ein weiterer Abstraktionsschritt ...

// API

const comp = (f, g) => x => f(g(x));
const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x];

// some data

const xs = [1,2,3];

// applying

console.log(
  foldr(comp(concat, sqr_)) ([]) (xs) // [1, 4, 9]
);

Und noch einer:

// API

const concatMap = f => foldr(comp(concat, f)) ([]);
const comp = (f, g) => x => f(g(x));
const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x];

// some data

const xs = [1,2,3];

// applying

console.log(
  concatMap(sqr_) (xs) // [1, 4, 9]
);

Das zugrunde liegende Prinzip sollte jetzt klar sein. Ich bin immer noch unzufrieden mit concatMap, weil es nur mit Arrays funktioniert. Ich möchte, dass es mit jedem Datentyp arbeitet, der faltbar ist:

// API

const concatMap = foldr => f => foldr(comp(concat, f)) ([]);
const concat = xs => ys => xs.concat(ys);
const sqr_ = x => [x * x];
const comp = (f, g) => x => f(g(x));

// Array

const xs = [1, 2, 3];

const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);

// Option (another foldable data type)

const None =      r => f => r;
const Some = x => r => f => f(x);

const foldOption = f => acc => tx => tx(acc) (x => f(x) (acc));

// applying

console.log(
  concatMap(foldr) (sqr_) (xs), // [1, 4, 9]
  concatMap(foldOption) (sqr_) (Some(3)), // [9]
  concatMap(foldOption) (sqr_) (None) // []
);

I erweiterte die Anwendung von concatMap auf eine größere Domäne von Datentypen, dh alle faltbaren Datentypen. Die Generalisierung betont die Gemeinsamkeiten zwischen verschiedenen Typen (oder Objekten, Entitäten).

Dies habe ich durch Wörterbuch-Passing erreicht (concatMap_s zusätzliches Argument in meinem Beispiel). Jetzt ist es etwas ärgerlich, diese Art von Diktaten in Ihrem Code herumzugeben. Daher führten die Haskell-Leute Typklassen ein, ... äh, über Typenschildern abstrakt:

concatMap :: Foldable t => (a -> [b]) -> t a -> [b]

concatMap (\x -> [x * x]) ([1,2,3]) -- yields [1, 4, 9]
concatMap (\x -> [x * x]) (Just 3) -- yields [9]
concatMap (\x -> [x * x]) (Nothing) -- yields []

Daher profitiert Haskells generisches concatMap sowohl von Abstraktion als auch von Verallgemeinerung.

2
user6445533

Abstraktion

Abstraktion spezifiziert das Framework und verbirgt die Informationen zur Implementierungsebene. Die Konkretheit wird auf der Abstraktion aufgebaut. Sie erhalten einen Plan, den Sie beim Implementieren der Details beachten müssen. Die Abstraktion reduziert die Komplexität, indem Details auf niedriger Ebene ausgeblendet werden.

Beispiel: Ein Drahtgittermodell eines Autos.

Verallgemeinerung

Die Generalisierung verwendet eine "is-a" -Beziehung von einer Spezialisierung zur Generalisierungsklasse. Übliche Struktur und Verhalten werden von der Spezialisierung bis zur generalisierten Klasse verwendet. Auf einer sehr breiteren Ebene können Sie dies als Vererbung verstehen. Warum ich unter dem Begriff Erbschaft verstehe, können Sie diesen Begriff sehr gut in Beziehung setzen. Verallgemeinerung wird auch als „Ist-Beziehung“ bezeichnet.

Beispiel: Es gibt eine Klasse namens Person. Ein Student ist eine Person. Eine Fakultät ist eine Person. Daher ist hier die Beziehung zwischen Student und Person, ähnlich wie Fähigkeit und Person, eine Verallgemeinerung.

2
Cornel Marian

Bei der Abstraktion geht es normalerweise darum, die Komplexität zu reduzieren, indem unnötige Details eliminiert werden. Eine abstrakte Klasse in OOP ist beispielsweise eine übergeordnete Klasse, die allgemeine Merkmale ihrer untergeordneten Elemente enthält, jedoch nicht die genaue Funktionalität angibt.

Bei der Generalisierung müssen nicht unbedingt Details vermieden werden, sondern es muss ein Mechanismus vorhanden sein, der es ermöglicht, dieselbe Funktion auf verschiedene Argumente anzuwenden. Zum Beispiel können Sie bei polymorphen Typen in funktionalen Programmiersprachen sich nicht um die Argumente kümmern, sondern sich auf die Funktionsweise der Funktion konzentrieren. Ebenso können Sie in Java einen generischen Typ verwenden, der für alle Typen ein "Schirm" ist, wenn die Funktion gleich ist.

0
NeoSer

Lassen Sie mich das so einfach wie möglich erklären.

"Alle hübschen Mädchen sind weiblich." ist eine Abstraktion.

"Alle hübschen Mädchen schminken." ist eine Verallgemeinerung.

0
sidquanto