Чи можуть об'єкти, побудовані з одного класу, мати унікальні визначення методів?


14

Я знаю, що це здається дивним питанням, оскільки суть двох або більше об'єктів, що діляться в одному класі, полягає в тому, що їх поведінка однакова, тобто їхні методи однакові.

Однак мені цікаво, чи існують мови OOP, які дозволяють вам переосмислити методи об’єктів так само, як ви можете призначити різні значення для їх полів. Результатом стали б об'єкти, побудовані з одного класу, які вже не мають такої ж поведінки.

Якщо я не помиляюся, ви можете зробити цей JavaScript? Поряд з цим питанням я запитую, чому хтось хотів би це зробити?


9
Технічний термін "заснований на прототипі". Шукаючи його, ви отримаєте багато матеріалів про цей аромат OOP. (Будьте в курсі, що багато людей не вважають такі структури належними "класами" саме тому, що вони не мають єдиних атрибутів і методів.)
Кіліан Фот

1
ти маєш на увазі, як, як два різні екземпляри кнопки можуть робити різні речі при натисканні на них, тому що кожен з них пов'язаний до іншого обробника кнопок? Звичайно, завжди можна стверджувати, що вони роблять те саме - вони називають обробника кнопок. У будь-якому випадку, якщо ваша мова підтримує делегати або покажчики функцій, то це легко - у вас є певна змінна властивість / поле / член, яка містить вказівник делегата або функції, а реалізація методу викликає те, що ви йдете далеко.
Кейт Григорій

2
Це складно :) У мовах, де посилання на функції функцій стропінгу є загальними, легко передати якийсь код у setClickHandler()метод і змусити різні екземпляри одного класу робити дуже різні речі. Мовами, які не мають зручних лямбда-виразів, простіше створити новий анонімний підклас лише для нового обробника. Традиційно переважаючі методи вважалися ознакою нового класу, а встановлення значень атрибутів не було, але особливо з функціями обробника, вони мають дуже схожі ефекти, тому відмінністю стає війна за слова.
Кіліан Фот

1
Не зовсім те, що ви просите, але це звучить як шаблон дизайну стратегії. Тут у вас є екземпляр класу, який ефективно змінює його тип під час виконання. Це трохи поза темою, тому що це не мова, яка дозволяє це, але варто згадати, тому що ви сказали "чому хтось хотів би це зробити"
тоні

OOP на основі прототипу @Killian Forth не має класів за визначенням, і, таким чином, не зовсім відповідає тому, про що задається ОП. Ключова відмінність класу - це не екземпляр.
дорівнює

Відповіді:


9

Методи більшості (на основі класу) мов OOP фіксуються за типом.

JavaScript заснований на прототипі, а не на основі класу, тому ви можете переопрацьовувати методи на основі поодиноких випадків, оскільки немає чіткого розрізнення між класом та об'єктом; насправді "клас" в JavaScript - це об'єкт, який є як шаблон для того, як повинні працювати екземпляри.

Будь-яка мова, що дозволяє виконувати функції першого класу, Scala, Java 8, C # (через делегатів) тощо, може діяти так, як у вас є відміни методу для кожного примірника; вам слід було б визначити поле з типом функції, а потім змінити його в кожному екземплярі.

У Scala є ще одна можливість; У Scala ви можете створити об'єктні одиночні клавіші (використовуючи ключове слово об'єкт замість ключового слова класу), так що ви можете розширити свій клас і переопрацювати методи, в результаті чого новий екземпляр цього базового класу буде замінений.

Чому хтось це зробив? Причин може бути десятки. Можливо, поведінка потребує більш чіткого визначення, ніж це дозволить використання різних комбінацій полів. Він також міг би підтримувати код, відокремлений і краще організований. Однак загалом я вважаю, що ці випадки рідше, і часто існує більш просте рішення з використанням значень поля.


Ваше перше речення не має сенсу. Що стосується OOP на основі класу з фіксованими типами?
Бергі

Я маю на увазі, що для кожного типу набір та визначення методів є фіксованими або іншими словами, щоб додати чи змінити метод, ви повинні створити новий тип (наприклад, шляхом підтипу).
дорівнює

@Bergi: У OOP на базі класу слова "class" та "type" по суті означають те саме. Оскільки не має сенсу дозволяти типу integer мати значення "привіт", також немає сенсу мати тип Employee, щоб мати значення або методи, які не належать до класу Employee.
slebetman

@slebetman: Я мав на увазі, що мова на базі класу OOP не обов'язково має сильне, статично закріплене введення.
Бергі

@Bergi: Ось сенс "більшість" у відповіді вище (більшість означає не всі). Я просто коментую ваш коментар "що це має робити".
slebetman

6

Важко здогадатися про мотивацію вашого питання, і тому деякі можливі відповіді можуть чи не відповідатимуть вашим реальним інтересам.

Навіть у деяких непрообразних мовах можна наблизити цей ефект.

Наприклад, у Java анонімний внутрішній клас досить близький до того, що ви описуєте - ви можете створювати та інстанціювати підклас оригіналу, переосмислюючи лише метод чи методи, які ви хочете. Отриманий клас буде instanceofоригінальним класом, але не буде тим самим класом.

Щодо того, чому ви хочете це зробити? Що стосується лямбдаських виразів Java 8, я думаю, що багато кращих випадків використання проходять. Щонайменше, з більш ранніми версіями Java це дозволяє уникнути розповсюдження тривіальних класів з вузьким використанням. Тобто, якщо у вас є велика кількість суміжних випадків використання, що відрізняються лише крихітним функціональним способом, ви можете створювати їх майже на ходу (майже), з різницею поведінки, що вводиться в той момент, коли це потрібно.

Однак, навіть перед J8, це часто може бути відновлено, щоб перенести різницю в поле чи три та ввести їх у конструктор. З J8, звичайно, сам метод можна вводити в клас, хоча може виникнути спокуса зробити це, коли інший рефакторинг може бути чистішим (якщо не таким крутим).


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вираз буде обмежений відомими випадками функції диспетчеризації, тоді як методи можна додавати та видаляти будь-коли.

Також спеціалізація на екземплярах корисна для налагодження, коли ви хочете тимчасово перевірити, що відбувається з певним об'єктом у вашому запущеному додатку.


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.


Ви хочете розширити свою відповідь за допомогою модулів та методу Extension? (Просто показати деякі інші способи розширення об'єктів новими методами)?
кнут

@knut: Я майже впевнений, що extendнасправді просто створює клас singleton для об'єкта, а потім імпортує модуль у клас singleton.
Linuxios

5

Ви можете подумати про методи, що дозволяють вам зібрати власний клас під час виконання. Це може усунути безліч клейового коду, того коду, який не має іншої мети, як об'єднати два класи разом для спілкування один з одним. Міксини - дещо більш структуроване рішення тих же проблем.

Ти трохи страждаєш від парадоксу суєти , по суті, що важко побачити значення мовної функції, поки ти не використав цю функцію в реальній програмі. Тому шукайте можливості там, де ви думаєте, що це може спрацювати, спробуйте це і подивіться, що станеться.

Подивіться у свій код для груп класів, які відрізняються лише одним методом. Шукайте класи, єдиною метою яких є поєднання двох інших класів у різних комбінаціях. Шукайте методи, які нічого іншого не роблять, але передають дзвінок іншому об'єкту. Шукайте класи, які інстанціюються за допомогою складних креативних моделей . Це всі потенційні кандидати, які мають бути замінені методами, призначеними для інстанцій.


1

Інші відповіді показали, як це є загальною рисою динамічних об'єктно-орієнтованих мов, і як її можна тривіально емулювати статичною мовою, яка має об'єкти функцій першого класу (наприклад, делегати в c #, об'єкти, які переосмислюють оператора () у 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 включає механізми автоматичного оголошення відповідних типів. Такі механізми насправді не роблять нічого, чого не вдалося зробити, просто використовуючи віртуальні методи та необов'язково вкладені статичні класи, але вони значно зменшують кількість котлів, необхідних для створення класу, єдиною метою якого є запуск єдиного методу від імені ще один клас.


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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.