42

我知道抽象是指把一些更具体的东西变得更抽象。那东西可能是数据结构或过程。例如:

  1. 数据抽象:矩形是正方形的抽象。它专注于正方形有两对相对边的事实,而忽略了正方形的相邻边相等的事实。
  2. 过程抽象:高阶函数map是过程的抽象,它对值列表执行一些操作以生成全新的值列表。它专注于过程循环遍历列表的每个项目以生成新列表的事实,并忽略对列表的每个项目执行的实际操作。

所以我的问题是:抽象与泛化有何不同?我正在寻找主要与函数式编程相关的答案。但是,如果在面向对象编程中有相似之处,那么我也想了解这些。

4

8 回答 8

46

确实是一个非常有趣的问题。我发现这篇关于该主题的文章,简明扼要地指出:

抽象通过隐藏不相关的细节来降低复杂性,而泛化通过用单个构造替换执行相似功能的多个实体来降低复杂性。

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

Book(title, ISBN, borrowed)

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


另一方面,泛化并不试图删除细节,而是使功能适用于更广泛(更通用)的项目范围。通用容器是这种心态的一个很好的例子:你不想编写 , 等的实现StringListIntList这就是为什么你宁愿编写一个适用于所有类型的通用List[T]List (就像在 Scala 中一样)。请注意,您没有抽象列表,因为您没有删除任何细节或操作,您只是使它们普遍适用于您的所有类型。

第二轮

@dtldarek 的回答真是一个很好的例证!基于它,这里有一些代码可能会提供进一步的说明。

还记得Book我提到的吗?当然,图书馆中还有其他可以借用的东西(我将调用所有这些对象的集合,Borrowable即使这可能甚至不是一个词:D):

所有这些项目都将在我们的数据库和业务逻辑中具有抽象表示,可能类似于我们的Book. 此外,我们可以定义一个所有Borrowables 共有的特征:

trait Borrowable {
    def itemId:Long
}

然后我们可以编写适用于所有s 的通用Borrowable逻辑(此时我们不在乎它是一本书还是一本杂志):

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

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

客户是否借用了一本书、一本杂志或一张 DVD 并不重要。它总是相同的过程。

因此,我们通过将可以借用的所有东西定义为s来概括借用项目的操作。Borrowable

于 2013-10-10T11:16:14.087 回答
40

目的:

门户蛋糕照片

抽象:

蛋糕符号

概括:

许多甜点

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++ 的重载函数形式)之间存在非平凡的联系某种意义上的一种抽象)。

致谢:Solo 的门户蛋糕。djttwo的甜品桌。该符号是来自material.io的蛋糕图标。

于 2013-10-19T09:30:30.013 回答
6

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

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

概括

文章指出:

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

因此,例如,如果您将泛化应用于形状,则所有类型的形状的共同属性是面积和周长。

因此,广义形状(例如 Shape)和它的特化(例如圆形)可以用如下类表示(请注意,此图像取自上述文章)

在此处输入图像描述

同样,如果您在喷气式飞机领域工作,则可以将 Jet 作为概括,它具有翼展属性。喷气式飞机的专业化可能是战斗机,它将继承翼展属性并具有其自己的战斗机独有的属性,例如 NumberOfMissiles。

抽象

文章将抽象定义为:

“识别具有系统变化的共同模式的过程;抽象代表共同模式并提供指定使用哪个变化的方法”(理查德加布里埃尔)“

在编程领域:

抽象类是允许继承但永远不能实例化的父类。

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

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

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

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

于 2013-10-13T20:23:17.923 回答
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
于 2013-10-18T13:52:27.920 回答
2

抽象

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

示例:汽车的线框模型。

概括

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

示例:假设存在一个名为 Person 的类。学生是一个人。教师是一个人。因此这里的学生与人的关系,同样教师与人的关系是泛化的。

于 2013-10-19T18:49:08.007 回答
2

我想为尽可能多的观众提供答案,因此我使用网络通用语言 Javascript。

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

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

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

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

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

另一个抽象步骤...

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

还有一个:

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

基本原则现在应该很清楚了。不过我还是不满意concatMap,因为它只适用于Arrays. 我希望它适用于所有可折叠的数据类型:

// 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以涵盖更大的数据类型领域,即所有可折叠数据类型。泛化强调不同类型(或者更确切地说是对象、实体)之间的共性。

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

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受益于抽象和泛化。

于 2017-06-24T13:01:12.660 回答
1

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

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

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

于 2013-10-19T09:32:18.540 回答
0

抽象通常是通过消除不必要的细节来降低复杂性。例如,OOP 中的抽象类是一个父类,它包含其子类的共同特征,但没有指定确切的功能。

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

于 2013-10-19T16:31:30.457 回答