oop - sociales - pensamiento abstracto ejemplos
¿Cuál es la diferencia entre abstracción y generalización? (8)
Abstracción
La abstracción es especificar el marco y ocultar la información del nivel de implementación. El concreto se construirá sobre la abstracción. Le proporciona un plan a seguir para implementar los detalles. La abstracción reduce la complejidad al ocultar detalles de bajo nivel.
Ejemplo: un modelo de marco de alambre de un automóvil.
Generalización
La generalización utiliza una relación "is-a" desde una especialización a la clase de generalización. La estructura y el comportamiento comunes se usan desde la especialización hasta la clase generalizada. En un nivel muy amplio, puedes entender esto como herencia. Por qué considero que el término herencia es, puedes relacionar este término muy bien. La generalización también se llama una relación "Es-a".
Ejemplo: Considera que existe una clase llamada Persona. Un estudiante es una persona. Una facultad es una persona. Por lo tanto, aquí la relación entre el alumno y la persona, del mismo modo la facultad y la persona es generalización.
Entiendo que la abstracción se trata de tomar algo más concreto y hacerlo más abstracto. Ese algo puede ser una estructura de datos o un procedimiento. Por ejemplo:
- Abstracción de datos: un rectángulo es una abstracción de un cuadrado. Se concentra en el hecho de que un cuadrado tiene dos pares de lados opuestos e ignora el hecho de que los lados adyacentes de un cuadrado son iguales.
- Abstracción de procedimiento: el
map
función de orden superior es una abstracción de un procedimiento que realiza un conjunto de operaciones en una lista de valores para producir una lista de valores completamente nueva. Se concentra en el hecho de que el procedimiento recorre cada elemento de la lista para producir una nueva lista e ignora las operaciones reales realizadas en cada elemento de la lista.
Entonces mi pregunta es esta: ¿cómo es la abstracción diferente de la generalización? Estoy buscando respuestas relacionadas principalmente con la programación funcional. Sin embargo, si hay paralelos en la programación orientada a objetos, me gustaría aprender sobre ellos también.
Déjame explicarte de la manera más simple posible.
"Todas las chicas bonitas son mujeres". es una abstracción
"Todas las chicas bonitas se maquillaron". es una generalización.
La abstracción generalmente consiste en reducir la complejidad eliminando detalles innecesarios. Por ejemplo, una clase abstracta en OOP es una clase principal que contiene características comunes de sus hijos pero no especifica la funcionalidad exacta.
La generalización no requiere necesariamente evitar detalles, sino más bien tener algún mecanismo que permita aplicar la misma función a diferentes argumentos. Por ejemplo, los tipos polimórficos en los lenguajes de programación funcional le permiten no preocuparse por los argumentos, más bien se centran en el funcionamiento de la función. Del mismo modo, en java puede tener un tipo genérico que es un "paraguas" para todos los tipos, mientras que la función es la misma.
Me gustaría ofrecer una respuesta para la mayor audiencia posible, por lo tanto, uso la Lingua Franca de la web, Javascript.
Comencemos con una pieza ordinaria de código imperativo:
// 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]
En el siguiente paso, presento la abstracción más importante en la programación: funciones. Funciones abstractas sobre expresiones:
// 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]
)
Como puede ver, muchos detalles de implementación se abstraen. Abstracción significa la supresión de detalles .
Otro paso de abstracción ...
// 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]
);
Y otro:
// 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]
);
El principio subyacente debería ser ahora claro. Todavía estoy insatisfecho con concatMap
, porque solo funciona con Array
s. Quiero que funcione con todos los tipos de datos que son plegables:
// 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) // []
);
concatMap
la aplicación de concatMap
para abarcar un dominio más amplio de tipos de datos, por ejemplo, todos los tipos de datos plegables. La generalización enfatiza las similitudes entre diferentes tipos (o más bien objetos, entidades).
concatMap
esto por medio de pasar el diccionario (el argumento adicional de concatMap
en mi ejemplo). Ahora es un poco molesto pasar estos tipos de dicts a lo largo de su código. Por lo tanto, la gente de Haskell introdujo clases de tipos para ... um, abstraer sobre tipos de caracteres:
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 []
Entonces, el concatMap
genérico de concatMap
beneficia tanto de la abstracción como de la generalización.
No abordar la fuente creíble / oficial: un ejemplo en Scala
Tener "abstracción"
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"
}
y "Generalización"
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)
ejecutando
specialized(StringContainer)
slightlyGeneralized(stringContainer)
generalized(new IntContainer(12))
evenMoreGeneral(new { val detail = 3.141 })
lleva a
It''s a StringContainer: Unflexible
It''s a String container: Any string
It''s a Int container: 12
It''s something detailed: 3.141
Una pregunta muy interesante de hecho. Encontré este artículo sobre el tema, que establece de manera concisa que:
Si bien la abstracción reduce la complejidad al ocultar detalles irrelevantes, la generalización reduce la complejidad al reemplazar entidades múltiples que realizan funciones similares con una sola construcción.
Tomemos el ejemplo anterior de un sistema que administra libros para una biblioteca. Un libro tiene toneladas de propiedades (número de páginas, peso, tamaño de fuente (s), portada, ...) pero para el propósito de nuestra biblioteca, es posible que solo necesitemos
Book(title, ISBN, borrowed)
Acabamos de abstraer de los libros reales de nuestra biblioteca, y solo tomamos las propiedades que nos interesaban en el contexto de nuestra aplicación.
La generalización, por otro lado, no trata de eliminar detalles, sino de hacer que la funcionalidad sea aplicable a un rango de artículos más amplio (más genérico). Los contenedores genéricos son un muy buen ejemplo para esa mentalidad: no desea escribir una implementación de StringList
, IntList
, etc., por lo que prefiere escribir una lista genérica que se aplique a todos los tipos (como List[T]
en Scala). Tenga en cuenta que no ha abstraído la lista, ya que no eliminó ningún detalle u operación, simplemente los hizo genéricamente aplicables a todos sus tipos.
La ronda 2
¡La respuesta de @dtldarek es realmente una muy buena ilustración! En base a esto, aquí hay un código que puede proporcionar una mayor aclaración.
¿Recuerdas el Book
que mencioné? Por supuesto, hay otras cosas en una biblioteca que uno puede pedir prestadas (Llamaré al conjunto de todos esos objetos Borrowable
aunque probablemente ni siquiera sea una palabra: D):
http://f.cl.ly/items/3z0f1S3g1h1m2u3c0l0g/diagram.png
Todos estos elementos tendrán una representación abstracta en nuestra base de datos y lógica comercial, probablemente similar a la de nuestro Book
. Además, podríamos definir un rasgo que sea común a todos los Borrowable
s:
trait Borrowable {
def itemId:Long
}
Luego podríamos escribir una lógica generalizada que se aplique a todos los Borrowable
(en ese momento no nos importa si es un libro o una revista):
object Library {
def lend(b:Borrowable, c:Customer):Receipt = ...
[...]
}
En resumen: almacenamos una representación abstracta de todos los libros, revistas y DVD en nuestra base de datos, porque una representación exacta no es factible ni necesaria. Luego seguimos adelante y dijimos
No importa si un cliente pide prestado un libro, una revista o un DVD. Siempre es el mismo proceso.
Por lo tanto, generalizamos la operación de tomar prestado un artículo, definiendo todas las cosas que uno puede tomar prestado como Borrowable
.
Voy a usar algunos ejemplos para describir la generalización y la abstracción, y voy a referirme a this artículo.
Que yo sepa, no existe una fuente oficial para la definición de abstracción y generalización en el dominio de programación (Wikipedia es probablemente lo más cercano que se puede llegar a una definición oficial, en mi opinión), así que he usado un artículo que considero creíble.
Generalización
El artículo dice que:
"El concepto de generalización en OOP significa que un objeto encapsula el estado común y el comportamiento de una categoría de objetos".
Entonces, por ejemplo, si aplica la generalización a las formas, entonces las propiedades comunes para todos los tipos de formas son el área y el perímetro.
Por lo tanto, una forma generalizada (por ejemplo, forma) y sus especializaciones (por ejemplo, un círculo), se pueden representar en las clases de la siguiente manera (tenga en cuenta que esta imagen se ha tomado del artículo mencionado anteriormente)
Del mismo modo, si estuvieras trabajando en el dominio de los aviones a reacción, podrías tener un Jet como generalización, que tendría una propiedad de envergadura. Una especialización de un Jet podría ser un FighterJet, que heredaría la propiedad de envergadura y tendría su propia propiedad exclusiva para los aviones de combate, por ejemplo, NumberOfMissiles.
Abstracción
El artículo define la abstracción como:
"el proceso de identificación de patrones comunes que tienen variaciones sistemáticas, una abstracción representa el patrón común y proporciona un medio para especificar qué variación usar" (Richard Gabriel) "
En el dominio de la programación:
Una clase abstracta es una clase principal que permite la herencia pero nunca se puede crear una instancia.
Por lo tanto, en el ejemplo dado en la sección de Generalización anterior, una Forma es abstracta como:
En el mundo real, nunca se calcula el área o el perímetro de una forma genérica, se debe saber qué tipo de forma geométrica se tiene porque cada forma (por ejemplo, cuadrado, círculo, rectángulo, etc.) tiene su propia área y fórmulas de perímetro.
Sin embargo, además de ser abstracta, una forma también es una generalización (porque "encapsula el estado común y el comportamiento de una categoría de objetos", donde en este caso los objetos son formas).
Volviendo al ejemplo que di sobre Jets y FighterJets, un Jet no es abstracto ya que una instancia concreta de un Jet es factible, ya que uno puede existir en el mundo real, a diferencia de una forma, es decir, en el mundo real no puedes tener una forma que mantener una instancia de una forma, por ejemplo, un cubo. Entonces en el ejemplo del avión, un Jet no es abstracto, es una generalización ya que es posible tener una instancia "concreta" de un jet.
Objeto:
Abstracción:
Generalización:
Ejemplo en Haskell:
La implementación de la ordenación por selección utilizando la cola de prioridad con tres interfaces diferentes:
- una interfaz abierta con la cola implementada como una lista ordenada,
- una interfaz abstraída (para que los detalles estén ocultos detrás de la capa de abstracción),
- una interfaz generalizada (los detalles aún son visibles, pero la implementación es más 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
El código también está disponible a través de pastebin .
Digno de notar son los tipos existenciales. Como @lukstafi ya señaló, la abstracción es similar al cuantificador existencial y la generalización es similar al cuantificador universal. Observe que hay una conexión no trivial entre el hecho de que ∀xP (x) implica ∃xP (x) (en un universo no vacío), y que raramente hay una generalización sin abstracción (incluso funciones de sobrecarga de tipo c ++) una especie de abstracción en cierto sentido).
Créditos: Portal cake by Solo . Mesa de djttwo por djttwo . El símbolo se basó en la obra de arte del Portal .