Подтвердить что ты не робот

Как утка печатается по-разному от старого типа "вариант" и/или интерфейсов?

Я продолжаю видеть, как фраза "утиная набивка" обрушилась и даже пробежала пример кода или два. Я тоже слишком занят, чтобы делать свои собственные исследования, может кто-то сказать мне, кратко:

  • разница между "типом утки" и типом "старый вариант" и
  • укажите пример того, где я мог бы предпочел, чтобы утка печаталась по варианту ввода и
  • укажите пример того, что я должен был бы использовать для печати утки?

duck typing illustration courtesy of The Register

Я не хочу казаться домашней птицей, сомневаясь в силе этой "новой" конструкции, и я не уклоняюсь от этой проблемы, отказываясь делать исследования, но я встряхиваю при всем стекающем обмане, видели в последнее время. Похоже на то, что я не набираю текст (ака динамической печати), поэтому сразу не вижу преимуществ.

ДОБАВЛЕНИЕ: Спасибо за примеры до сих пор. Мне кажется, что использование чего-то типа "O- > can (Blah)" эквивалентно поиску отражения (что, вероятно, не так), и/или примерно такое же, как и высказывание (O - это IBlah), который компилятор может быть в состоянии проверить для вас, но последнее имеет преимущество отличия моего интерфейса IBlah от вашего интерфейса IBlah, в то время как другие два нет. Конечно, имея много крошечных интерфейсов, плавающих вокруг для каждого метода, будет беспорядочно, но опять же, так что можно проверить множество индивидуальных методов...

... так что я просто не понимаю. Это фантастическая экономия времени или такая же старая вещь в новом мешке? Где пример, который требует утиной печати?

4b9b3361

Ответ 1

Простой ответ - вариант слабо типизирован, тогда как утиная типизация строго типизирована.

Утиная печать может быть скомбинирована так: "если она ходит как утка, выглядит как утка, действует как утка, то это утка". Это означает, что утка является следующим интерфейсом.

interface IDuck {
  void Quack();
}

Теперь рассмотрим Daffy

class Daffy {
  void Quack() {
    Console.WriteLine("Thatsssss dispicable!!!!");
  }
}

В этом случае Daffy на самом деле не IDuck. Но он действует точно так же, как утка. Почему заставить Daffy реализовать IDuck, когда совершенно очевидно, что Daffy на самом деле утка.

Здесь вы можете ввести Duck typing. Это позволяет безопасное преобразование типа между любым типом, который имеет все поведение IDuck и ссылки IDuck.

IDuck d = new Daffy();
d.Quack();

Теперь метод Quack можно вызвать на "d" с полной безопасностью типа. В этом вызове или вызове метода нет вероятности ошибки типа времени выполнения.

Ответ 2

В некоторых ответах здесь я видел неправильное использование терминологии, из-за чего люди давали неправильные ответы.

Итак, прежде чем дать ответ, я приведу несколько определений:

  1. Сильно набрано

    Язык строго типизирован, если он обеспечивает безопасность типов программ. Это означает, что он гарантирует две вещи: то, что называется прогрессом, и то, что называется сохранением. Прогресс в основном означает, что все "правильно типизированные" программы могут фактически выполняться компьютером, они могут аварийно завершить работу, вызвать исключение или выполнить бесконечный цикл, но на самом деле они могут быть запущены. Сохранение означает, что если программа "правильно напечатана", то она всегда будет "правильно напечатана" и что ни одна переменная (или область памяти) не будет содержать значение, которое не соответствует назначенному типу.

    Большинство языков имеют свойство "progress". Однако многие не удовлетворяют свойству "сохранения". Хорошим примером является C++ (и C тоже). Например, в C++ возможно заставить любой адрес памяти вести себя так, как если бы он был любого типа. Это в основном позволяет программистам нарушать систему типов в любое время. Вот простой пример:

    struct foo
    {
        int x;
        iny y;
        int z;
    }
    
    char * x = new char[100];
    foo * pFoo = (foo *)x;
    foo aRealFoo;
    *pFoo = aRealFoo;
    

    Этот код позволяет кому-то взять массив символов и записать в него экземпляр "foo". Если бы C++ был строго напечатан, это было бы невозможно. Безопасные языки типа, такие как С#, Java, VB, lisp, ruby, python и многие другие, могут вызвать исключение, если вы попытаетесь привести массив символов к экземпляру "foo".

  2. Слабый тип

    Что-то слабо напечатано, если оно не сильно напечатано.

  3. Статически типизированный

    Язык статически типизирован, если его система типов проверена во время компиляции. Статически типизированный язык может быть либо "слабо типизированным", например C, либо строго типизированным, например С#.

  4. Динамически набираемый

    Динамически типизированный язык - это язык, где типы проверяются во время выполнения. Во многих языках есть какая-то смесь между статической и динамической типизацией. С#, например, будет динамически проверять множество приведений во время выполнения, потому что невозможно проверить их во время компиляции. Другими примерами являются такие языки, как Java, VB и Objective-C.

    Есть также некоторые языки, которые "полностью" или "в основном" динамически типизированы, такие как "lisp", "ruby" и "small talk"

  5. Утка печатает

    Утиная печать - это нечто, полностью ортогональное статической, динамической, слабой или строгой типизации. Это практика написания кода, который будет работать с объектом независимо от его базовой идентичности типа. Например, следующий код VB.NET:

    function Foo(x as object) as object
        return x.Quack()
    end function
    

    Будет работать независимо от типа объекта, который передается в "Foo", при условии, что он определяет метод "Quack". То есть, если объект выглядит как утка, ходит как утка и говорит как утка, то это утка. Печатание утки приходит во многих формах. Можно иметь статическую типизацию утки, динамическую типизацию утки, строгую типизацию утки и слабую типизацию утки. Шаблонные функции C++ являются хорошим примером "слабой статической типизации утки". Пример, показанный в посте "JaredPar", показывает пример "сильной статической типизации утки". Позднее связывание в VB (или в коде на Ruby или Python) обеспечивает "сильную динамическую типизацию утки".

  6. Вариант

    Вариант представляет собой динамически типизированную структуру данных, которая может содержать диапазон предопределенных типов данных, включая строки, целочисленные типы, даты и com-объекты. Затем он определяет набор операций для назначения, преобразования и манипулирования данными, хранящимися в вариантах. Является ли вариант строго типизированным, зависит от языка, на котором он используется. Например, вариант в программе VB 6 строго типизирован. Среда выполнения VB гарантирует, что операции, написанные в коде VB, будут соответствовать правилам ввода для вариантов. Попытка добавить строку в IUnknown через тип варианта в VB приведет к ошибке во время выполнения. Однако в C++ варианты слабо типизированы, поскольку все типы C++ слабо типизированы.

Хорошо... теперь, когда я получил определения, я теперь могу ответить на ваш вопрос:

Вариант, в VB 6, позволяет одну из форм печатания утки. Существуют более эффективные способы печатания уток (пример с Jared Par - один из лучших), чем варианты, но вы можете делать типизацию утиными вариантами. То есть вы можете написать один фрагмент кода, который будет работать с объектом независимо от его базового типа.

Тем не менее, выполнение этого с вариантами не дает большой проверки. Механизм статического типа типа утки, подобный тому, который описывает JaredPar, дает преимущества типизации утки, а также некоторую дополнительную проверку компилятором. Это может быть очень полезно.

Ответ 3

Утиная печать - еще один термин для динамического набора текста или позднего связывания. Вариантный объект, который анализирует/компилирует любой доступ к члену (например, obj.Anything), который может или не может быть определен во время выполнения, - это утиная печать.

Ответ 4

Вероятно, ничто не требует утиной печати, но в определенных ситуациях это может быть удобно. Скажем, у вас есть метод, который берет и использует объект запечатанной утки класса из какой-то сторонней библиотеки. И вы хотите сделать метод проверяемым. У Duck есть очень большой API (вроде ServletRequest), из которого вам нужно только заботиться о небольшом подмножестве. Как вы его протестируете?

Один из способов - заставить метод взять что-то, что делает шарлатанство. Затем вы можете просто создать крякирующий макет объекта.

Ответ 5

Попробуйте прочитать первый абзац статьи в Википедии о наборе уток.
Утиная печать в Википедии

У меня может быть интерфейс (IRunnable), который определяет метод Run().
Если у меня есть другой класс с таким методом:
public void RunSomeRunnable (IRunnable rn) {...}

В дружественном языке типа утки я мог бы пройти в любом классе с методом Run() в методе RunSomeRunnable().
В статически типизированном языке класс, передаваемый в RunSomeRunnable, должен явно реализовать интерфейс IRunnable.

"Если это Run() как утка"

вариант скорее похож на объект в .NET.

Ответ 6

@Kent Fredric

Ваш пример, безусловно, может быть выполнен без утиного ввода с использованием явных интерфейсов... уродливый да, но это не невозможно.

И лично я нахожу наличие четко определенных контрактов в интерфейсах намного лучше для обеспечения качества кода, чем полагаться на утиную печать... но это только мое мнение и взять его с куском соли.

public interface ICreature { }
public interface IFly { fly();}
public interface IWalk { walk(); }
public interface IQuack { quack(); }
// ETC

// Animal Class
public class Duck : ICreature, IWalk, IFly, IQuack
{
    fly() {};
    walk() {};
    quack() {};
}

public class Rhino: ICreature, IWalk
{
    walk();
}

// In the method
List<ICreature> creatures = new List<ICreature>();
creatures.Add(new Duck());
creatures.Add(new Rhino());   

foreach (ICreature creature in creatures)
{
    if (creature is IFly)        
         (creature as IFly).fly();        
    if (creature is IWalk) 
         (creature as IWalk).walk();         
}
// Etc

Ответ 7

Что касается вашего запроса на пример чего-то, что вам нужно использовать для печати утки, я не думаю, что такая вещь существует. Я думаю об этом, как о том, следует ли использовать рекурсию или использовать итерацию. Иногда один работает лучше, чем другой.

По моему опыту, утиная печать делает код более понятным и понятным (как для программиста, так и для читателя). Но я считаю, что более традиционная статическая типизация устраняет много ненужных ошибок ввода. Просто нет способа объективно сказать, что один лучше другого или даже сказать, какие ситуации более эффективны, чем другие.

Я говорю, что если вам удобно использовать статическую типизацию, используйте ее. Но вы должны, по крайней мере, попробовать утиную распечатку (и использовать ее в нетривиальном проекте, если это возможно).

Чтобы ответить вам более прямо:

... так что я просто не понимаю. Это фантастическая экономия времени или такая же старая вещь в новом мешке?

Это и то, и другое. Вы все еще атакуете одни и те же проблемы. Ты просто делаешь это по-другому. Иногда это действительно все, что вам нужно сделать, чтобы сэкономить время (даже если не по какой-либо другой причине заставить себя думать о том, чтобы делать что-то по-другому).

Это панацея, которая спасет все человечество от вымирания? Нет. И любой, кто говорит вам иначе, является фанатом.

Ответ 8

Я думаю, что основной точкой утиного набора является то, как он используется. Один использует обнаружение метода и интроспекцию объекта, чтобы знать, что с ним делать, вместо того, чтобы заранее заявлять, что будет (где вы знаете, что с ним делать).

Это, вероятно, более практично в языках OO, где примитивы не являются примитивами, а являются объектами.

Я думаю, что лучший способ подвести итог, в варианте типа, сущность есть/может быть чем угодно, и то, что она является неопределенным, в отличие от сущности, только выглядит как что угодно, но вы можете решить, что это такое спросив его.

Здесь что-то, что я не верю, правдоподобно без утипов.

sub dance { 
     my $creature = shift;
     if( $creature->can("walk") ){ 
         $creature->walk("left",1);
         $creature->walk("right",1); 
         $creature->walk("forward",1);
         $creature->walk("back",1);
     }
     if( $creature->can("fly") ){ 
          $creature->fly("up"); 
          $creature->fly("right",1); 
          $creature->fly("forward",1); 
          $creature->fly("left", 1 ); 
          $creature->fly("back", 1 ); 
          $creature->fly("down");
     } else if ( $creature->can("walk") ) { 
         $creature->walk("left",1);
         $creature->walk("right",1); 
         $creature->walk("forward",1);
         $creature->walk("back",1);
     } else if ( $creature->can("splash") ) { 
         $creature->splash( "up" ) for ( 0 .. 4 ); 
     }
     if( $creature->can("quack") ) { 
         $creature->quack();
     }
 }

 my @x = ();  
 push @x, new Rhinoceros ; 
 push @x, new Flamingo; 
 push @x, new Hyena; 
 push @x, new Dolphin; 
 push @x, new Duck;

 for my $creature (@x){

    new Thread(sub{ 
       dance( $creature ); 
    }); 
 }

Любой другой способ потребовал бы, чтобы вы наложили ограничения типа для функций, которые бы вырезали разные виды, и вам нужно было создавать разные функции для разных видов, что делает код действительно адским для поддержания.

И это действительно засасывает с точки зрения просто попытки выполнить хорошую хореографию.

Ответ 9

Вариант (по крайней мере, как я использовал их в VB6) содержит переменную одного, четко определенного, обычно статического типа. Например, он может содержать int или float или строку, но в качестве ints используются варианты ints, в качестве float используются варианты float, а в качестве строк используются варианты строк.

Вместо того, чтобы использовать утиную печать, используется динамическая типизация. При наборе уток переменная может использоваться как int, или float, или строка, если это помогает поддерживать определенные методы, поддерживаемые int или float или string в определенном контексте.

Пример вариантов по сравнению с утиным типом:

Для веб-приложения предположим, что я хочу, чтобы моя информация пользователя поступала из LDAP, а не из базы данных, но я все еще хочу, чтобы моя пользовательская информация могла быть использована остальной частью веб-структуры, которая основана на базе данных и ОРМ.

Использование вариантов: Не повезло. Я могу создать вариант, который может содержать объект UserFromDbRecord или объект UserFromLdap, но объекты UserFromLdap не будут использоваться подпрограммами, которые ожидают объекты из иерархии FromDbRecord.

Использование утиного ввода: я могу взять свой класс UserFromLdap и добавить несколько методов, которые заставляют его действовать как класс UserFromDbRecord. Мне не нужно реплицировать весь интерфейс FromDbRecord, достаточно для тех процедур, которые мне нужно использовать. Если я сделаю это правильно, это чрезвычайно мощный и гибкий метод. Если я ошибаюсь, он создает очень запутывающий и хрупкий код (подвержен поломке, если изменяется библиотека БД или библиотека LDAP).

Ответ 10

Все, что вы можете сделать с помощью утиной печати, также можно использовать с интерфейсами. Утиная печать быстро и удобно, но некоторые утверждают, что это может привести к ошибкам (если два разных метода/свойства называются одинаковыми). Интерфейсы безопасны и ясны, но люди могут сказать "зачем говорить о очевидности?". Отдых - это пламя. Каждый выбирает то, что ему подходит, и никто не "прав".