我理解抽象是关于采取更具体的东西并使其更抽象。这可能是数据结构或过程。例如:
map
是一个过程的抽象,它对值列表执行一组操作以生成一个全新的值列表。它集中在这样一个事实,即程序循环遍历列表中的每个项目,以便生成一个新列表,并忽略对列表中每个项目执行的实际操作。所以我的问题是:抽象与泛化有什么不同?我正在寻找主要与函数式编程相关的答案。但是,如果在面向对象编程中有相似之处,那么我也想了解它们。
答案 0 :(得分:30)
确实是一个非常有趣的问题。我在这个主题上找到了this article,简明扼要地说:
虽然抽象通过隐藏不相关的细节来降低复杂性,但泛化通过用单个构造替换执行类似函数的多个实体来降低复杂性。
让我们看一个管理图书馆书籍的系统的旧例子。一本书有很多属性(页数,重量,字体大小,封面,......)但是为了我们图书馆的目的,我们可能只需要
Book(title, ISBN, borrowed)
我们刚刚从我们图书馆的真实书籍中抽象出来,并且只在我们的应用程序环境中获取了我们感兴趣的属性。
另一方面,泛化不会尝试删除细节,而是使功能适用于更广泛(更通用)的项目范围。通用容器是这种思维方式的一个很好的例子:你不想写StringList
,IntList
的实现,等等,这就是为什么你要写一个通用列表适用于所有类型(如Scala中的List[T]
)。请注意,您尚未抽象列表,因为您没有删除任何详细信息或操作,您只是将它们通常适用于所有类型。
@ 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)
<强>对象强>
<强>抽象:强>
<强>概括强>
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 ++ - 就像重载函数一样)从某种意义上说是一种抽象。)
答案 2 :(得分:4)
我将使用一些例子来描述泛化和抽象,我将参考this文章。
据我所知,在编程领域中没有关于抽象和泛化定义的官方来源(维基百科可能是我认为最接近官方定义的),所以我反而使用了一篇文章我认为可信。
<强>概括强>
文章指出:
“OOP中的泛化概念意味着对象封装 一类对象的共同状态和行为。“
因此,例如,如果对图形应用泛化,则所有形状类型的公共属性都是面积和周长。
因此,广义形状(例如形状)及其特化(例如圆形)可以按如下类别表示(请注意,此图像取自上述文章)
同样地,如果你在喷气式飞机领域工作,你可以将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。
让我们从一段普通的命令性代码开始:
// 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;
在下一步中,我将介绍编程中最重要的抽象 - 函数。函数抽象表达式:
// 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;
正如您所看到的,很多实现细节都被抽象掉了。抽象意味着抑制细节。
另一个抽象步骤......
// 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;
还有一个:
// 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;
现在应该明确基本原则。我仍然对concatMap
不满意,因为它只适用于Array
。我希望它能够处理每种可折叠的数据类型:
// 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;
我扩展了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中,您可以使用泛型类型,它是所有类型的“保护伞”,而功能是相同的。