Могут ли объекты, созданные из одного и того же класса, иметь уникальные определения методов?

14

Я знаю, что это кажется странным вопросом, поскольку смысл в том, что два или более объектов совместно используют один и тот же класс, состоит в том, что их поведение одинаково, то есть их методы идентичны.

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

Если я не ошибаюсь, вы можете сделать этот JavaScript? Наряду с этим вопросом я спрашиваю: зачем кому-то это делать?

Нико Беллик
источник
9
Технический термин «на основе прототипа». Поиск этого даст вам много материала об этом аромате ООП. (Имейте в виду, что многие люди не считают такие структуры собственными «классами» именно потому, что они не имеют одинаковых атрибутов и методов.)
Килиан Фот,
1
Вы имеете в виду, например, как два разных экземпляра кнопки могут делать разные вещи при нажатии, потому что каждый из них подключен к разному обработчику кнопки? Конечно, всегда можно утверждать, что они делают одно и то же - они вызывают свой обработчик кнопок. В любом случае, если ваш язык поддерживает делегаты или указатели на функции, то это легко - у вас есть некоторая переменная свойства / поля / члена, которая содержит указатель на делегат или функцию, и реализация метода вызывает это, и все, что вам нужно.
Кейт Грегори
2
Это сложно :) В языках, где распространено обращение к функциям, легко передать некоторый код в setClickHandler()метод и заставить разные экземпляры одного и того же класса делать совершенно разные вещи. В языках, в которых нет удобных лямбда-выражений, проще создать новый анонимный подкласс только для нового обработчика. Традиционно, переопределение методов считалось отличительной чертой нового класса, в то время как установка значений атрибутов не была, но особенно с функциями-обработчиками, эти два эффекта очень похожи, поэтому различие становится войной за слова.
Килиан Фот
1
Не совсем то, что вы спрашиваете, но это звучит как шаблон проектирования стратегии. Здесь у вас есть экземпляр класса, который эффективно изменяет его тип во время выполнения. Это немного не по теме, потому что это не язык, который позволяет это, но стоит упомянуть, потому что вы сказали «зачем кому-то это делать»
Тони
ООП на основе прототипа @Killian Forth не имеет классов по определению и, следовательно, не совсем соответствует тому, о чем спрашивает OP. Ключевым отличием является класс, а не экземпляр.
конев

Ответы:

9

Методы в большинстве языков ООП (на основе классов) фиксируются по типу.

JavaScript основан на прототипах, а не на классах, и поэтому вы можете переопределять методы для каждого экземпляра, потому что нет четкого различия между «классом» и объектом; в действительности «класс» в JavaScript - это объект, который походит на шаблон для того, как экземпляры должны работать.

Любой язык, который допускает первоклассные функции, Scala, Java 8, C # (через делегаты) и т. Д., Может действовать так, как если бы вы переопределяли методы для каждого экземпляра; вам нужно будет определить поле с типом функции, а затем переопределить его в каждом экземпляре.

Скала имеет другую возможность; В Scala вы можете создавать синглеты объектов (используя ключевое слово object вместо ключевого слова class), поэтому вы можете расширить свой класс и переопределить методы, что приведет к созданию нового экземпляра этого базового класса с переопределениями.

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

eques
источник
Ваше первое предложение не имеет смысла. Как ООП на основе классов имеет отношение к фиксированным типам?
Берги
Я имею в виду, что для каждого типа набор и определения методов являются фиксированными или, другими словами, чтобы добавить или изменить метод, вы должны создать новый тип (например, путем подтипа).
коню
@Bergi: В ООП на основе классов слова «класс» и «тип» по сути означают одно и то же. Поскольку не имеет смысла разрешать типу integer иметь значение «hello», также не имеет смысла иметь тип Employee, чтобы иметь значения или методы, которые не принадлежат классу Employee.
slebetman
@slebetman: Я имел в виду, что язык ООП на основе классов не обязательно имеет строгую статическую типизацию.
Берги
@Bergi: Это значение слова «большинство» в ответе выше (большинство означает не все). Я просто комментирую ваш комментарий "что он должен делать".
Slebetman
6

Трудно угадать мотивацию вашего вопроса, и поэтому некоторые возможные ответы могут или не могут соответствовать вашим реальным интересам.

Даже в некоторых непрототипных языках этот эффект можно аппроксимировать.

Например, в Java анонимный внутренний класс очень близок к тому, что вы описываете - вы можете создать и создать экземпляр подкласса оригинала, переопределяя только тот метод или методы, которые вы хотите. Полученный класс будет instanceofисходным классом, но не будет тем же классом.

Относительно того, почему вы хотите это сделать? Я думаю, что с лямбда-выражениями Java 8 многие из лучших вариантов использования уходят. По крайней мере, в более ранних версиях Java это может избежать распространения тривиальных, узко используемых классов. То есть, когда у вас есть большое количество связанных вариантов использования, отличающихся лишь незначительным функциональным способом, вы можете создавать их практически на лету (почти), с поведенческими различиями, вводимыми в то время, когда вам это нужно.

Тем не менее, даже до J8, это часто может быть реорганизовано, чтобы сдвинуть разницу в поле или три, и вставить их в конструктор. С J8, конечно, сам метод может быть введен в класс, хотя может возникнуть соблазн сделать это, когда другой рефакторинг может быть чище (если не так круто).

schnitz
источник
6

Вы запросили любой язык, который предоставляет методы для каждого экземпляра. Ответ на Javascript уже есть, поэтому давайте посмотрим, как это делается в Common Lisp, где вы можете использовать EQL-специализаторы:

;; define a class
(defclass some-class () ())

;; declare a generic method
(defgeneric some-method (x))

;; specialize the method for SOME-CLASS
(defmethod some-method ((x some-class)) 'default-result)

;; create an instance named *MY-OBJECT* of SOME-CLASS
(defparameter *my-object* (make-instance 'some-class))

;; specialize SOME-METHOD for that specific instance
(defmethod some-method ((x (eql *my-object*))) 'specific-result)

;; Call the method on that instance
(some-method *my-object*)
=> SPECIFIC-RESULT

;; Call the method on a new instance
(some-method (make-instance 'some-class))
=> DEFAULT-RESULT

Почему?

Специалисты по EQL полезны, когда предполагается, что аргумент, подлежащий диспетчеризации, имеет тип, для которого eqlимеет смысл: число, символ и т. Д. Вообще говоря, он вам не нужен, и вам просто нужно определить столько подклассов, сколько нужна ваша проблема. Но иногда вам нужно только выполнить диспетчеризацию в соответствии с параметром, который является, например, символом: caseвыражение будет ограничено известными случаями в функции диспетчеризации, тогда как методы можно добавлять и удалять в любое время.

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

CoreDump
источник
5

Вы также можете сделать это в Ruby, используя одноэлементные объекты:

class A
  def do_something
    puts "Hello!"
  end
end

obj = A.new
obj.do_something

def obj.do_something
  puts "Hello world!"
end

obj.do_something

Производит:

Hello!
Hello world!

Что касается использования, то именно так Ruby выполняет методы класса и модуля. Например:

def SomeClass
  def self.hello
    puts "Hello!"
  end
end

Фактически определяет одноэлементный метод helloна Classобъекте SomeClass.

Linuxios
источник
Вы хотите расширить свой ответ модулями и exte-method? (Просто чтобы показать некоторые другие способы расширения объектов новыми методами)?
Кнут
@knut: Я почти уверен, что на extendсамом деле просто создает класс singleton для объекта, а затем импортирует модуль в класс singleton.
Linuxios
5

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

Вы немного страдаете от парадокса blub , по сути, из-за того , что трудно увидеть значение языковой функции, пока вы не используете эту функцию в реальной программе. Так что ищите возможности, где вы думаете, что это может работать, попробуйте и посмотрите, что произойдет.

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

Карл Билефельдт
источник
1

Другие ответы показали, как это является общей особенностью динамических объектно-ориентированных языков, и как это можно тривиально эмулировать в статическом языке, который имеет функциональные объекты первого класса (например, делегаты в c #, объекты, которые переопределяют operator () в c ++) , В статических языках, в которых такая функция отсутствует, это сложнее, но все же может быть достигнуто путем использования комбинации шаблона стратегии и метода, который просто делегирует его реализацию стратегии. По сути, это то же самое, что вы делаете в c # с делегатами, но синтаксис немного сложнее.

Жюль
источник
0

Вы можете сделать что-то подобное в C # и большинстве других подобных языков.

public class MyClass{
    public Func<A,B> MyABFunc {get;set;}
    public Action<B> MyBAction {get;set;}
    public MyClass(){
        //todo assign MyAFunc and MyBAction
    }
}
Эсбен Сков Педерсен
источник
1
Программисты о концептуальных вопросах, и ответы должны объяснить вещи. Создание дампов кода вместо объяснения похоже на копирование кода из IDE на доску: это может показаться знакомым и даже иногда понятным, но это кажется странным ... просто странным. У доски нет компилятора
gnat
0

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

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

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

Supercat
источник
0

Я считаю, что это определение «динамического» языка, такого как ruby, groovy & Javascript (и многие другие). Динамический относится (по крайней мере частично) к способности динамически определять, как экземпляр класса может вести себя на лету.

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

Это упрощает некоторые хитрые операции, такие как Monkey-Patching, где вы можете настроить экземпляр класса, чтобы позволить вам взаимодействовать с закрытой библиотекой так, как они этого не ожидали.

Билл К
источник
0

Я не говорю, что это хорошо, но в Python это возможно. Я не могу придумать хороший пример использования, но я уверен, что они существуют.

    class Foo(object):
        def __init__(self, thing):
            if thing == "Foo":
                def print_something():
                    print "Foo"
            else:
                def print_something():
                    print "Bar"
            self.print_something = print_something

    Foo(thing="Foo").print_something()
    Foo(thing="Bar").print_something()
Singletoned
источник