抽象和泛化之间有什么区别?

时间:2013-10-10 09:19:34

标签: oop functional-programming abstraction nomenclature generalization

我理解抽象是关于采取更具体的东西并使其更抽象。这可能是数据结构或过程。例如:

  1. 数据抽象:矩形是正方形的抽象。它集中在一个正方形有两对相对边的事实,它忽略了一个正方形的相邻边相等的事实。
  2. 过程抽象:高阶函数map是一个过程的抽象,它对值列表执行一组操作以生成一个全新的值列表。它集中在这样一个事实,即程序循环遍历列表中的每个项目,以便生成一个新列表,并忽略对列表中每个项目执行的实际操作。
  3. 所以我的问题是:抽象与泛化有什么不同?我正在寻找主要与函数式编程相关的答案。但是,如果在面向对象编程中有相似之处,那么我也想了解它们。

8 个答案:

答案 0 :(得分:30)

确实是一个非常有趣的问题。我在这个主题上找到了this article,简明扼要地说:

  

虽然抽象通过隐藏不相关的细节来降低复杂性,但泛化通过用单个构造替换执行类似函数的多个实体来降低复杂性。

让我们看一个管理图书馆书籍的系统的旧例子。一本书有很多属性(页数,重量,字体大小,封面,......)但是为了我们图书馆的目的,我们可能只需要

Book(title, ISBN, borrowed)

我们刚刚从我们图书馆的真实书籍中抽象出来,并且只在我们的应用程序环境中获取了我们感兴趣的属性。


另一方面,泛化不会尝试删除细节,而是使功能适用于更广泛(更通用)的项目范围。通用容器是这种思维方式的一个很好的例子:你不想写StringListIntList的实现,等等,这就是为什么你要写一个通用列表适用于所有类型(如Scala中的List[T])。请注意,您尚未抽象列表,因为您没有删除任何详细信息或操作,您只是将它们通常适用于所有类型。

第2轮

@ dtldarek的答案真是一个很好的例子!在此基础上,这里提供了一些可能进一步澄清的代码。

记得我提到的Book?当然,库中还有其他东西可以借用(我会调用所有这些对象Borrowable的集合,即使这可能不是一个字:D):

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

所有这些项目在我们的数据库和业务逻辑中都有一个抽象表示,可能类似于我们的Book。另外,我们可能会定义一个对所有Borrowable s共同的特征:

trait Borrowable {
    def itemId:Long
}

然后我们可以编写适用于所有Borrowable广义逻辑(此时我们不关心它是一本书还是一本杂志):

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

总结:我们在我们的数据库中存储了所有书籍,杂志和DVD的抽象表示,因为精确的表示既不可行也不必要。然后我们继续说道

  

客户是否借阅书籍,杂志或DVD并不重要。它总是一样的过程。

因此,我们概括借用项目的操作,通过定义可以借用Borrowable s的所有内容。

答案 1 :(得分:28)

<强>对象

portal cake photo

<强>抽象:

enter image description here

<强>概括

many desserts

Haskell中的示例:

通过使用具有三个不同接口的优先级队列来实现选择排序:

  • 一个开放接口,队列实现为排序列表,
  • 一个抽象的接口(所以细节隐藏在抽象层后面),
  • 一个通用接口(细节仍然可见,但实现更灵活)。
{-# 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

该代码也可通过pastebin获得。

值得注意的是存在主义类型。正如@lukstafi已经指出的那样,抽象类似于存在量词,而泛化类似于通用量词。 观察到∀xP(x)暗示∃xP(x)(在非空的宇宙中)之间存在一个非平凡的联系,并且很少有没有抽象的泛化(即使是c ++ - 就像重载函数一样)从某种意义上说是一种抽象。)

<强>现金: 门户蛋糕Solodjttwo的甜点表。 该符号基于Portal艺术品。

答案 2 :(得分:4)

我将使用一些例子来描述泛化和抽象,我将参考this文章。

据我所知,在编程领域中没有关于抽象和泛化定义的官方来源(维基百科可能是我认为最接近官方定义的),所以我反而使用了一篇文章我认为可信。

<强>概括

文章指出:

  

“OOP中的泛化概念意味着对象封装   一类对象的共同状态和行为。“

因此,例如,如果对图形应用泛化,则所有形状类型的公共属性都是面积和周长。

因此,广义形状(例如形状)及其特化(例如圆形)可以按如下类别表示(请注意,此图像取自上述文章)

enter image description here

同样地,如果你在喷气式飞机领域工作,你可以将Jet作为概括,这将具有翼展属性。 Jet的专业化可以是FighterJet,它将继承翼展属性,并且具有战斗机独有的属性,例如: NumberOfMissiles。

<强>抽象

本文将抽象定义为:

  

“识别具有系统性的常见模式的过程   变化;抽象代表了共同的模式并提供了   指定使用哪种变体的方法“(Richard Gabriel)”

在编程领域:

  

抽象类是允许继承但可以继承的父类   永远不会被实例化。

因此,在上面的“泛化”部分中给出的示例中,Shape是抽象的:

  

在现实世界中,你永远不会计算a的面积或周长   通用形状,你必须知道你有什么样的几何形状   因为每个形状(例如正方形,圆形,矩形等)都有自己的形状   面积和周长公式。

然而,除了抽象之外,形状也是一种泛化(因为它“封装了一类对象的常见状态和行为”,在这种情况下,对象是形状)。

回到我给出的关于Jets和FighterJets的例子,Jet不是抽象的,因为Jet的具体实例是可行的,因为在现实世界中可以存在,不像在现实世界中你不能保持的形状你持有形状实例的形状,例如一个立方体。所以在飞机的例子中,Jet并不是抽象的,它是一种概括,因为它可能具有喷射的“具体”实例。

答案 3 :(得分:2)

不解决可信/官方来源:Scala中的一个例子

有“抽象”

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

和“泛化”

  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)
执行

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

导致

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

答案 4 :(得分:2)

抽象

抽象是指定框架并隐藏实现级别信息。具体性将建立在抽象之上。它为您提供了在实施细节时遵循的蓝图。抽象通过隐藏低级细节来降低复杂性。

示例:汽车的线框模型。

概括

泛化使用从专业化到泛化类的“is-a”关系。从专业化到广义类使用共同的结构和行为。在更广泛的层面上,您可以将其理解为继承。为什么我接受继承这个术语,你可以很好地理解这个术语。泛化也称为“Is-a”关系。

示例:考虑存在一个名为Person的类。学生就是一个人。教师是一个人。因此,在这里,学生与人之间的关系,同样是教师与人之间的关系是一般化的。

答案 5 :(得分:2)

我想为最受欢迎的观众提供答案,因此我使用了网络的Lingua Franca,Javascript。

让我们从一段普通的命令性代码开始:

&#13;
&#13;
// 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]
&#13;
&#13;
&#13;

在下一步中,我将介绍编程中最重要的抽象 - 函数。函数抽象表达式:

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

正如您所看到的,很多实现细节都被抽象掉了。抽象意味着抑制细节

另一个抽象步骤......

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

还有一个:

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

现在应该明确基本原则。我仍然对concatMap不满意,因为它只适用于Array。我希望它能够处理每种可折叠的数据类型:

&#13;
&#13;
// 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) // []
);
&#13;
&#13;
&#13;

扩展了concatMap的应用程序以包含更大的数据类型域,nameley所有可折叠数据类型。泛化强调不同类型之间的共性(或者更确切地说是对象,实体)。

我通过字典传递(concatMap在我的例子中附加参数)实现了这一点。现在,在整个代码中传递这些类型的dicts有点烦人。因此,Haskell人员引入了类型类,...嗯,抽象类型dicts:

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

因此,Haskell的通用concatMap从抽象和泛化中受益。

答案 6 :(得分:0)

让我以最简单的方式解释。

“所有漂亮女孩都是女性。”是一种抽象。

“所有漂亮女孩都化妆。”是一种概括。

答案 7 :(得分:0)

抽象通常是通过消除不必要的细节来降低复杂性。例如,OOP中的抽象类是包含其子项的常用功能的父类,但未指定确切的功能。

泛化不一定要求避免细节,而是要有一些机制允许将相同的函数应用于不同的参数。例如,函数式编程语言中的多态类型允许您不必担心参数,而是关注函数的操作。类似地,在java中,您可以使用泛型类型,它是所有类型的“保护伞”,而功能是相同的。