web-dev-qa-db-fra.com

Quelle est la différence entre abstraction et généralisation?

Je comprends que l’abstraction consiste à prendre quelque chose de plus concret et à le rendre plus abstrait. Ce quelque chose peut être une structure de données ou une procédure. Par exemple:

  1. Abstraction de données: Un rectangle est une abstraction d'un carré. Il se concentre sur le fait qu'un carré a deux paires de côtés opposés et ignore le fait que les côtés adjacents d'un carré sont égaux.
  2. Abstraction de procédure: La fonction d'ordre supérieur map est une abstraction d'une procédure qui exécute un ensemble d'opérations sur une liste de valeurs pour produire une liste de valeurs entièrement nouvelle. Il se concentre sur le fait que la procédure parcourt chaque élément de la liste afin de générer une nouvelle liste et ignore les opérations effectives effectuées sur chaque élément de la liste.

Ma question est donc la suivante: en quoi l’abstraction est-elle différente de la généralisation? Je cherche des réponses principalement liées à la programmation fonctionnelle. Cependant, s'il existe des parallèles dans la programmation orientée objet, j'aimerais en apprendre davantage à ce sujet également.

35
Aadit M Shah

Objet:

portal cake photo

Abstraction:

enter image description here

Généralisation:

many desserts

Exemple en Haskell:

La mise en œuvre du tri de sélection en utilisant une file d'attente prioritaire avec trois interfaces différentes:

  • une interface ouverte avec la file d'attente étant implémentée sous forme de liste triée,
  • une interface abstraite (les détails sont cachés derrière la couche d'abstraction),
  • une interface généralisée (les détails sont toujours visibles, mais la mise en œuvre est plus flexible).
{-# 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

Le code est également disponible via Pastebin .

Il convient de noter les types existentiels. Comme @lukstafi l’a déjà souligné, l’abstraction est semblable au quantificateur existentiel et la généralisation à un quantificateur universel . Notez qu’il existe un lien non trivial entre le fait que ∀xP (x) implique xP (x) un univers non vide), et qu’il existe rarement une généralisation sans abstraction (même les fonctions surchargées, similaires à celles de C++, forment en quelque sorte une sorte d’abstraction).

Crédits: Gâteau de portail par Solo . Table de desserts par djttwo . Le symbole était basé sur le Portal artwork.

26
dtldarek

Une question très intéressante en effet. J'ai trouvé cet article sur le sujet, qui précise que: 

Alors que l'abstraction réduit la complexité en masquant des détails non pertinents, la généralisation en réduit le remplacement en remplaçant plusieurs entités effectuant des fonctions similaires par une seule construction.

Prenons l’ancien exemple d’un système qui gère les livres d’une bibliothèque. Un livre a des tonnes de propriétés (nombre de pages, poids, taille de police, couverture, ...), mais pour les besoins de notre bibliothèque, nous n’avons peut-être besoin que de

Book(title, ISBN, borrowed)

Nous venons de résumer les vrais livres de notre bibliothèque et n'avons pris que les propriétés qui nous intéressaient dans le contexte de notre application. 


La généralisation, en revanche, ne cherche pas à supprimer les détails mais à rendre la fonctionnalité applicable à une gamme plus large (plus générique) d’articles. Les conteneurs génériques sont un très bon exemple de cet état d'esprit: vous ne voudriez pas écrire une implémentation de StringList, IntList, etc., c'est pourquoi vous préférez écrire un générique Liste qui s'applique à tous les types (comme List[T] dans Scala). Notez que vous n'avez pas résumé _ liste, car vous n'avez supprimé aucun détail ni aucune opération, vous les avez simplement rendues génériques et applicables à tous vos types.

2ème round

La réponse de @ dtldarek est vraiment une très bonne illustration! Sur cette base, voici un code qui pourrait fournir des éclaircissements supplémentaires.

Souvenez-vous de la Book que j'ai mentionnée? Bien sûr, il y a d'autres choses dans une bibliothèque que l'on peut emprunter (j'appellerai l'ensemble de tous ces objets Borrowable même si ce n'est probablement même pas un mot: D):

http://f.cl.ly/items/3z0f1S3g1h1m2u3c0l0g/diagram.png

Tous ces éléments auront une représentation abstract dans notre base de données et une logique métier, probablement similaire à celle de notre Book. De plus, nous pourrions définir un trait commun à tous les Borrowables:

trait Borrowable {
    def itemId:Long
}

Nous pourrions alors écrire la logique généralisée qui s'applique à toutes les Borrowables (à ce stade, nous ne nous soucions pas de savoir s'il s'agit d'un livre ou d'un magazine):

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

Pour résumer: Nous avons stocké une représentation abstraite de tous les livres, magazines et DVD de notre base de données, car une représentation exacte n’est ni réalisable ni nécessaire. Nous sommes ensuite allés de l'avant et avons dit

Peu importe qu'un livre, un magazine ou un DVD soit emprunté par un client. C'est toujours le même processus.

Ainsi, nous avons généralisé l’emprunt d’un article en définissant tout ce que l’on peut emprunter comme Borrowables.

30
fresskoma

Je vais utiliser quelques exemples pour décrire la généralisation et l’abstraction, et je vais faire référence à this article.

À ma connaissance, il n'existe pas de source officielle pour la définition de l'abstraction et de la généralisation dans le domaine de la programmation (Wikipedia est probablement la plus proche que vous obtiendrez à une définition officielle à mon avis), j'ai donc plutôt utilisé un article que je considère comme crédible.

Généralisation

L'article dit que:

"Le concept de généralisation dans OOP signifie qu’un objet encapsule un état et un comportement communs pour une catégorie d’objets."

Ainsi, par exemple, si vous appliquez la généralisation aux formes, les propriétés communes à tous les types de formes sont l'aire et le périmètre.

Par conséquent, une forme généralisée (par exemple, une forme) et ses spécialisations (par exemple, un cercle) peuvent être représentées dans des classes comme suit (notez que cette image a été prise à partir de l'article susmentionné)

enter image description here

De même, si vous travailliez dans le domaine des avions à réaction, vous pourriez généraliser un jet, ce qui aurait une propriété d'envergure. Une spécialisation d'un avion à réaction peut être un avion de chasse, qui hériterait de la propriété de l'envergure et aurait sa propre propriété propre aux avions de combat, par exemple. NumberOfMissiles.

Abstraction

L'article définit l'abstraction comme:

"le processus d'identification de modèles communs comportant des variations systématiques; une abstraction représente le modèle commun et fournit un moyen de spécifier quelle variation utiliser" (Richard Gabriel) "

Dans le domaine de la programmation:

Une classe abstraite est une classe parente qui permet l'héritage mais peut ne jamais être instancié.

Par conséquent, dans l'exemple donné dans la section de généralisation ci-dessus, une forme est abstraite comme:

Dans le monde réel, vous ne calculez jamais la surface ou le périmètre d'un forme générique, vous devez savoir quel type de forme géométrique vous avez parce que chaque forme (par exemple, un carré, un cercle, un rectangle, etc.) a son propre formules de surface et de périmètre.

Cependant, en plus d’être abstraite, une forme est aussi une généralisation _ (parce qu’elle "encapsule les états et comportements communs pour une catégorie d’objets", dans ce cas les objets sont des formes).

Pour revenir à l’exemple que j’ai donné à propos des Jets et des FighterJets, un jet n’est pas abstrait, mais un exemple concret de jet est réalisable, car on peut exister dans le monde réel, contrairement à une forme, c’est-à-dire qu’il est impossible de conserver une forme. tenir une instance d'une forme, par exemple un cube. Ainsi, dans l'exemple de l'aéronef, un jet n'est pas abstrait, il s'agit d'une généralisation puisqu'il est possible d'avoir une instance "concrète" d'un jet. 

4
Ben Smith

Ne pas aborder les sources crédibles/officielles: un exemple à Scala

Avoir "Abstraction"

  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"
  }

et "généralisation"

  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)

l'exécution

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

mène à

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

J'aimerais offrir une réponse au plus grand nombre de personnes possible. C'est pourquoi j'utilise la Lingua Franca du Web, Javascript.

Commençons par un morceau de code impératif ordinaire:

// 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]

Dans l'étape suivante, je présente l'abstraction la plus importante dans la programmation: les fonctions. Fonctions abstraites sur les expressions:

// 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]
)

Comme vous pouvez le constater, de nombreux détails d’implémentation sont résumés. Abstraction signifie la suppression de détails .

Une autre étape de l'abstraction ...

// 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]
);

Et un autre:

// 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]
);

Le principe sous-jacent devrait maintenant être clair. Je ne suis toujours pas satisfait de concatMap cependant, car cela ne fonctionne qu'avec Arrays. Je veux que cela fonctionne avec tous les types de données qui sont pliables:

// 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 a élargi l'application of concatMap pour englober un plus grand domaine de types de données, nommément tous les types de données pliables. La généralisation met l'accent sur les points communs entre différents types (ou plutôt objets, entités).

J'y suis parvenu en passant par le dictionnaire (l'argument supplémentaire de concatMap dans mon exemple). Maintenant, il est quelque peu ennuyeux de faire circuler ces caractères dans votre code. Par conséquent, les gens de Haskell ont introduit les classes de types dans, ... um, des résumés abstraits sur des types:

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 []

Donc la variable générique concatMap de Haskell bénéficie à la fois de l'abstraction et de la généralisation.

2
user6445533

Abstraction

L'abstraction spécifie le cadre et masque les informations de niveau d'implémentation. Le concret sera construit au-dessus de l'abstraction. Il vous donne un plan à suivre lors de la mise en œuvre des détails. L'abstraction réduit la complexité en cachant les détails de bas niveau.

Exemple: Un modèle de voiture en fil de fer.

Généralisation

La généralisation utilise une relation «is-a» d’une spécialisation à la classe de généralisation. La structure et le comportement communs sont utilisés de la spécialisation à la classe généralisée. À un niveau très large, vous pouvez comprendre cela comme un héritage. Pourquoi je prends le terme héritage, vous pouvez très bien comprendre ce terme. La généralisation s'appelle également une relation «Est-un».

Exemple: considérons qu'il existe une classe nommée Person. Un étudiant est une personne. Une faculté est une personne. Par conséquent, ici, la relation entre étudiant et personne, de même que faculté et personne, est une généralisation.

2
Cornel Marian

L'abstraction consiste généralement à réduire la complexité en éliminant les détails inutiles. Par exemple, une classe abstraite dans OOP est une classe parent qui contient les fonctionnalités communes de ses enfants mais ne spécifie pas la fonctionnalité exacte.

La généralisation ne nécessite pas nécessairement d'éviter les détails, mais plutôt de disposer d'un mécanisme permettant d'appliquer la même fonction à des arguments différents. Par exemple, les types polymorphes dans les langages de programmation fonctionnels vous permettent de ne pas vous soucier des arguments, mais de vous concentrer sur le fonctionnement de la fonction. De même, en Java, vous pouvez avoir un type générique qui est un "parapluie" pour tous les types alors que la fonction est la même.

0
NeoSer

Laissez-moi vous expliquer de la manière la plus simple possible.

"Toutes les jolies filles sont des femmes." est une abstraction.

"Toutes les jolies filles se maquillent." est une généralisation.

0
sidquanto