鸭子类型在软件开发中意味着什么?


当前回答

简单的解释

鸭子打字是什么?

“如果它走路像鸭子,嘎嘎叫像....等等”——是的,但这是什么意思??!

我们感兴趣的是“对象”能做什么,而不是它们是什么。

让我们用一个例子来分解它:

详情见下文:

Duck Typing功能示例:

想象我有一根魔杖。它有特殊的力量。如果我挥舞魔杖,对一辆车说“开车!”,那么,它就会开车!

它对其他东西有用吗?不确定,所以我在卡车上试了试。哇,它也能开车!然后我在飞机上、火车上和1 Woods(这是一种人们用来“驾驶”高尔夫球的高尔夫球杆)上尝试了一下。他们都开车!

但它能用在茶杯上吗?错误:KAAAA-BOOOOOOM !结果不太好。====>茶杯不能开车!!咄! ?

这就是duck typing的基本概念。这是一个先试后买的系统。如果有效,一切都好。但如果失败了,就像手榴弹还在你手上一样,它会在你脸上爆炸。

换句话说,我们感兴趣的是对象能做什么,而不是对象是什么。

那么像c#或Java等语言呢?

如果我们关心的对象实际上是什么,那么我们的魔术只会对预先设置的,授权的类型起作用——在这种情况下是汽车,但对其他可以驾驶的对象无效:卡车,轻便摩托车,突突车等。它不能在卡车上工作,因为我们的魔杖期望它只能在汽车上工作。

换句话说,在这种情况下,魔杖非常仔细地观察物体是什么(它是一辆汽车吗?),而不是物体能做什么(例如汽车、卡车等能不能开车)。

让卡车驾驶的唯一方法是,如果您能够以某种方式让魔杖同时期待卡车和汽车(也许通过“实现一个公共接口”)。这可以通过一种叫做“多态”的技术来实现,也可以通过使用“接口”来实现——它们是一样的东西。如果你喜欢漫画,想要一个解释,看看我关于界面的漫画。

总结:关键外卖

在duck类型中,重要的是对象实际上可以做什么,而不是对象是什么。

序言

我试图通过去除迂腐的细微差别和学术语言来保持它的简单/有趣。这种方法并不适合所有人——如果你更喜欢学术定义,可以看看维基百科上关于“回避输入”的文章,或者《心灵捕手》中马特·达蒙对“回避输入”的解释;)

代码示例

但是高尔夫球杆怎么能像汽车一样“驾驶”呢?他们不一样吗?但如果你使用的是Ruby这样的语言:

class Car
   def drive
      "I"m driving a Car!"
   end
end

class GolfClub
   def drive
      "I"m driving a golf club!"
   end 
end

def test_drive(item)   
   item.drive # don't care what it is, all i care is that it can "drive"
end

car = Car.new
test_drive(car) #=> "I'm driving a Car"

club = GolfClub.new
test_drive(club) #=> "I"m driving a GolfClub"

其他回答

“鸭子打字”这个词是一个谎言。

“走路像鸭子,叫起来像鸭子,那就是鸭子”这句俗语在这里一次又一次地被重复着。

但这并不是鸭子打字(或者我们通常所说的鸭子打字)的意思。所有我们正在讨论的鸭子打字,是试图强制命令的东西。看看有什么东西是不是嘎嘎叫,不管它说什么。但是并没有推论出这个物体是不是鸭子。

For true duck typing, see type classes. Now that follows the idiom “If it walks like a duck and quacks like a duck then it is a duck.". With type classes, if a type implements all the methods that are defined by a type class, it can be considered a member of that type class (without having to inherit the type class). So, if there is a type class Duck which defines certain methods (quack and walk-like-duck), anything that implements those same methods can be considered a Duck (without needing to inherit Duck).

鸭子打字不是类型提示!

基本上,为了使用“duck typing”,你不会针对特定的类型,而是通过使用公共接口来针对更广泛的子类型(不是谈论继承,当我指的子类型时,我指的是适合相同配置文件的“事物”)。

你可以想象一个存储信息的系统。为了读写信息,你需要某种存储空间和信息。

存储类型可以是:文件、数据库、会话等。

无论存储类型是什么,该接口都会让您知道可用的选项(方法),这意味着在这一点上什么都没有实现!换句话说,接口不知道如何存储信息。

每个存储系统都必须通过实现接口的相同方法来知道接口的存在。

interface StorageInterface
{
   public function write(string $key, array $value): bool;
   public function read(string $key): array;
}


class File implements StorageInterface
{
    public function read(string $key): array {
        //reading from a file
    }

    public function write(string $key, array $value): bool {
         //writing in a file implementation
    }
}


class Session implements StorageInterface
{
    public function read(string $key): array {
        //reading from a session
    }

    public function write(string $key, array $value): bool {
         //writing in a session implementation
    }
}


class Storage implements StorageInterface
{
    private $_storage = null;

    function __construct(StorageInterface $storage) {
        $this->_storage = $storage;
    }

    public function read(string $key): array {
        return $this->_storage->read($key);
    }

    public function write(string $key, array $value): bool {
        return ($this->_storage->write($key, $value)) ? true : false;
    }
}

所以现在,每次你需要写/读信息时:

$file = new Storage(new File());
$file->write('filename', ['information'] );
echo $file->read('filename');

$session = new Storage(new Session());
$session->write('filename', ['information'] );
echo $session->read('filename');

在这个例子中,你最终在存储构造函数中使用Duck Typing:

function __construct(StorageInterface $storage) ...

希望能有所帮助;)

在duck类型中,对象的适用性(例如,在函数中使用)取决于是否实现了某些方法和/或属性,而不是基于该对象的类型。

例如,在Python中,len函数可用于任何实现__len__方法的对象。它并不关心该对象是否属于特定类型,例如字符串、列表、字典或MyAwesomeClass,只要这些对象实现了__len__方法,len将与它们一起工作。

class MyAwesomeClass:
    def __init__(self, str):
        self.str = str
    
    def __len__(self):
        return len(self.str)

class MyNotSoAwesomeClass:
    def __init__(self, str):
        self.str = str

a = MyAwesomeClass("hey")
print(len(a))  # Prints 3

b = MyNotSoAwesomeClass("hey")
print(len(b))  # Raises a type error, object of type "MyNotSoAwesomeClass" has no len()

换句话说,MyAwesomeClass看起来像鸭子,也像鸭子一样嘎嘎叫,因此是一只鸭子,而MyNotSoAwesomeClass看起来不像鸭子,也不嘎嘎叫,因此不是一只鸭子!

用鸭子打字技术的树遍历

def traverse(t):
    try:
        t.label()
    except AttributeError:
        print(t, end=" ")
    else:
        # Now we know that t.node is defined
        print('(', t.label(), end=" ")
        for child in t:
            traverse(child)
        print(')', end=" ")

维基百科有相当详细的解释:

http://en.wikipedia.org/wiki/Duck_typing

鸭子打字是一种动态的风格 输入一个对象的电流 方法和属性集 而是确定有效的语义 比它从一个特定的继承 类的实现 接口。

重要的是,使用duck类型时,开发人员可能更关心被使用的对象部分,而不是实际的底层类型是什么。