Duck typing and (java) interface concept
I just read the Wikipedia article about duck typing, and I feel like I miss an important point about the interface concept I used to in Java:
"When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bi开发者_如何学编程rd a duck."
class Duck:
def quack(self):
print("Quaaaaaack!")
def feathers(self):
print("The duck has white and gray feathers.")
def swim(self):
print("Swim seamlessly in the water")
class Person:
def quack(self):
print("The person imitates a duck.")
def feathers(self):
print("The person takes a feather from the ground and shows it.")
def name(self):
print("John Smith")
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
what if, in in_the_forest
, I write:
- does it
quack
like a duck ? yes - does it have a duck
feathers
? yes - great, it's a duck we've got !
and later, because I know it's a duck, I want it to swim
? john
will sink!
I don't want my application to crash (randomly) in the middle of its process just because John faked to be a duck, but I guess it wouldn't be a wise idea to check every single attributes of the object when I receive it ... ?
Duck typing isn't really about checking whether the things you need are there and then using them. Duck typing is about just using what you need.
The in_the_forest
function was written by a developer who was thinking about ducks. It was designed to operate on a Duck
. A Duck
can quack
and feathers
, so the coder used those features to get the job at hand done. In this case, the fact that a Duck
can also swim
wasn't used, and wasn't needed.
In a static language like Java, in_the_forest
would have been declared to take a Duck
. When the coder later discovered that they had a Person
(which could also quack
and feathers
) and wanted to reuse the function, they're out of luck. Is a Person
a subclass of Duck
? No, that doesn't seem at all appropriate. Is there a QuacksAndFeathers
interface? Maybe, if we're lucky. Otherwise we'll have to make one, go modify Duck
to implement it, and modify in_the_forest
to take a QuacksAndFeathers
instead of a Duck
. This may be impossible if Duck
is in an external library.
In Python, you just pass your Person to in_the_forest
and it works. Because it turns out in_the_forest
doesn't need a Duck
, it just needs a "duck-like" object, and in this case Person is sufficiently duck-like.
game
though, needs a different definition of "duck-like", which is slightly stronger. Here, John Smith is out of luck.
Now, it's true that Java would have caught this error at compile time and Python won't. That can be viewed as a disadvantage. The pro-dynamic-typing counter argument is to say that any substantial body of code you write will always contain bugs that no compiler can catch (and to be honest, Java isn't even a particularly good example of a compiler with strong static checks to catch lots of bugs). So you need to test your code to find those bugs. And if you're testing for those bugs, you will trivially find bugs where you passed a Person
to a function that needs a Duck
. Given that, the dynamic-typist says, a language that tempts you into not testing because it finds some of your trivial bugs is actually a bad thing. And on top of that, it prevents you from doing some really useful things, like reusing the in_the_forest
function on a Person
.
Personally I'm torn in two directions. I really like Python with its flexible dynamic typing. And I really like Haskell and Mercury for their powerful static type systems. I'm not much of a fan of Java or C++; in my opinion they have all of the bad bits of static typing with few of the good bits.
I don't want my application to crash (randomly) in the middle of its process just because John faked to be a duck, but I guess it wouldn't be a wise idea to check every single attributes of the object when I receive it ... ?
That's an issue of dynamic typing in general. In a statically typed language like Java, the compiler checks at compile time whether Person
implements IDuck
or not. In a dynamically typed language like Python, you get a run-time error if Person
misses some particular duck feature (such as swim
). To quote another Wikipedia article ("Type system", Section "Dynamic Typing"):
Dynamic typing may result in runtime type errors—that is, at runtime, a value may have an unexpected type, and an operation nonsensical for that type is applied. Such errors may occur long after the place where the programming mistake was made—that is, the place where the wrong type of data passed into a place it should not have. This may make the bug difficult to locate.
Dynamic typing has its drawbacks (you have mentioned one) and its advantages. A brief comparison can be found in another section of the Type system article in Wikipedia: Static and dynamic type checking in practice.
Can't speak for other languages, but in python it has been recently (v2.6) introduced the Abstract Base Classes (ABC) module.
If you read the rationale behind its introduction (PEP 3119) you will quickly realise that part of the reason was to "save john from sure death" or in other words, to facilitate the check on the fact when you program to an interface, all interface methods will be there. From the linked PEP:
ABCs are simply Python classes that are added into an object's inheritance tree to signal certain features of that object to an external inspector. Tests are done using isinstance(), and the presence of a particular ABC means that the test has passed. In addition, the ABCs define a minimal set of methods that establish the characteristic behavior of the type. Code that discriminates objects based on their ABC type can trust that those methods will always be present.
In general, you can apply the same pattern for your own code. For example: you can create a BasePlugin
class with all the methods needed for a plugin to work, and you can then create several different plugins by subclassing it. Depending on whether each plugin must or can have those methods defined, you can define BasePlugin
methods to silently pass (plugins can define those methods) or to raise an exception (plugins must define those methods / override the BasePlugin
's one).
EDIT: In the thread of comments below, I have been suggested to include in the answer this bit of discussion:
This kind of features - at least in python - are not implemented for the sake of the human programmer (python never silence an error, so there's already plenty of feedback there), but rather for the sake of python own introspection capability (thus making easier to write dynamic loading, metaprogramming code, etc...). In other words: I know John can't fly... but I want the python interpreter to know it too! :)
The dynamic typing strength, Duck Typing has another card up its sleeve, to enable implementing multiple interfaces, i.e. multiple independent behaviour traits.
Formal Interfaces, using ABCs are great and can gives you early failure. However you can still only implement one 'interface' at a time (you can inherit from one parent class).
Besides, using class inheritance you soon end up in a mess due to its innate chain and lead to tight coupling and blind alleys. An error due to a change up the chain will impact all the related code which may not have been tested nor have full test coverage.
Whereas classes that use composition instead promote independency. A dev can plan the UML with necessary interfaces to add multiple behaviour traits (e.g. ILogger, IEventGenerator, ICalcArea...) and in the code mask the multiple interfaces using duck typing. Of course the drawback is the dev needs to plan carefully to prevent name collisions and as the OP points out, missing a property or method will end as a runtime error rather than compiler warning.
Back to the old strength / weakness trade in.
精彩评论