Các đối tượng được xây dựng từ cùng một lớp có định nghĩa phương thức duy nhất không?


14

Tôi biết điều này có vẻ như là một câu hỏi kỳ lạ, vì quan điểm của hai hoặc nhiều đối tượng chia sẻ cùng một lớp là hành vi của họ giống nhau, tức là phương thức của họ giống hệt nhau.

Tuy nhiên, tôi tò mò liệu có bất kỳ ngôn ngữ OOP nào cho phép bạn xác định lại các phương thức của các đối tượng theo cùng một cách mà bạn có thể gán các giá trị khác nhau cho các trường của chúng. Kết quả sẽ là các đối tượng được xây dựng từ cùng một lớp không còn biểu hiện chính xác cùng một hành vi.

Nếu tôi không nhầm, bạn có thể làm JavaScript này không? Cùng với câu hỏi này, tôi hỏi, tại sao ai đó muốn làm điều này?


9
Thuật ngữ kỹ thuật là "dựa trên nguyên mẫu". Tìm kiếm nó sẽ cung cấp cho bạn nhiều tài liệu về hương vị này của OOP. (Xin lưu ý rằng nhiều người không coi các cấu trúc đó là "lớp" thích hợp, chính xác là vì họ không có các thuộc tính và phương thức thống nhất.)
Kilian Foth

1
bạn có nghĩa là làm thế nào hai trường hợp khác nhau của một nút có thể làm những việc khác nhau khi chúng được nhấp, bởi vì mỗi trường hợp chúng được nối với một trình xử lý nút khác nhau? Tất nhiên, người ta luôn có thể lập luận rằng họ làm điều tương tự - họ gọi trình xử lý nút của họ. Dù sao, nếu ngôn ngữ của bạn hỗ trợ các đại biểu hoặc con trỏ hàm thì điều này thật dễ dàng - bạn có một số biến thuộc tính / trường / thành viên chứa con trỏ ủy nhiệm hoặc hàm và việc thực hiện phương thức gọi bạn đi.
Kate Gregory

2
Thật phức tạp :) Trong các ngôn ngữ mà các tham chiếu hàm trượt xung quanh là phổ biến, thật dễ dàng truyền một số mã vào một setClickHandler()phương thức và làm cho các thể hiện khác nhau của cùng một lớp làm những việc rất khác nhau. Trong các ngôn ngữ không có biểu thức lambda thuận tiện, việc tạo một lớp con ẩn danh mới chỉ dành cho trình xử lý mới sẽ dễ dàng hơn. Theo truyền thống, các phương thức ghi đè được coi là đặc trưng của một lớp mới , trong khi thiết lập các giá trị của các thuộc tính thì không, nhưng đặc biệt là với các hàm xử lý, hai phương thức này có hiệu ứng rất giống nhau, vì vậy sự khác biệt trở thành một cuộc chiến về từ ngữ.
Kilian Foth

1
Không chính xác những gì bạn đang hỏi, nhưng điều này nghe có vẻ như mẫu thiết kế Chiến lược. Ở đây bạn có một thể hiện của một lớp có hiệu quả thay đổi kiểu của nó khi chạy. Nó hơi lạc đề vì nó không phải là ngôn ngữ cho phép điều này nhưng đáng nói vì bạn đã nói "tại sao ai đó muốn làm điều này"
tony

Định nghĩa OOP dựa trên nguyên mẫu @Killian Forth không có các lớp theo định nghĩa và do đó, không hoàn toàn khớp với những gì OP đang hỏi về. Sự khác biệt chính là một lớp không phải là một thể hiện.
eques

Câu trả lời:


9

Các phương thức trong hầu hết các ngôn ngữ OOP (dựa trên lớp) được cố định theo loại.

JavaScript dựa trên nguyên mẫu, không dựa trên lớp và do đó bạn có thể ghi đè các phương thức trên cơ sở theo từng trường hợp vì không có sự phân biệt cứng giữa "lớp" và đối tượng; trong thực tế, một "lớp" trong JavaScript là một đối tượng giống như một khuôn mẫu để biết các thể hiện nên hoạt động như thế nào.

Bất kỳ ngôn ngữ nào cho phép các hàm hạng nhất, Scala, Java 8, C # (thông qua các đại biểu), v.v., có thể hoạt động như bạn có các ghi đè phương thức theo từng thể hiện; bạn sẽ phải xác định một trường với một loại hàm và sau đó ghi đè lên nó trong từng trường hợp.

Scala có một tính tích cực khác; Trong Scala, bạn có thể tạo các singletons đối tượng (sử dụng từ khóa đối tượng thay vì từ khóa lớp), do đó bạn có thể mở rộng lớp của mình và ghi đè các phương thức, dẫn đến một thể hiện mới của lớp cơ sở đó có ghi đè.

tại sao một người có thể làm việc này? Có thể có hàng tá lý do. Nó có thể là hành vi cần cho tôi xác định chặt chẽ hơn so với việc sử dụng các kết hợp trường khác nhau sẽ cho phép. Nó cũng có thể giữ cho mã được tách rời và tổ chức tốt hơn. Tuy nhiên, nói chung, tôi nghĩ những trường hợp này hiếm hơn và thường có một giải pháp đơn giản hơn bằng cách sử dụng các giá trị trường.


Câu đầu tiên của bạn không có ý nghĩa. OOP dựa trên lớp phải làm gì với các loại cố định?
Bergi

Ý tôi là mỗi loại, tập hợp và định nghĩa của các phương thức là cố định hoặc nói cách khác, để thêm hoặc thay đổi một phương thức, bạn phải tạo một kiểu mới (ví dụ: bằng cách phân nhóm).
eques

@Bergi: Trong lớp dựa trên OOP, từ "lớp" và "loại" về cơ bản có nghĩa là cùng một điều. Vì việc cho phép kiểu số nguyên có giá trị "xin chào" là không hợp lý, nên việc nhân viên có các giá trị hoặc phương thức không thuộc về lớp Nhân viên cũng không có nghĩa gì.
slebetman

@slebetman: Ý tôi là một ngôn ngữ OOP dựa trên lớp không nhất thiết phải có kiểu gõ mạnh, được thi hành tĩnh.
Bergi

@Bergi: Đó là ý nghĩa của "nhất" trong câu trả lời ở trên (hầu hết có nghĩa là không phải tất cả). Tôi chỉ đang bình luận về bình luận "bạn phải làm gì".
slebetman

6

Thật khó để đoán động cơ cho câu hỏi của bạn và vì vậy một số câu trả lời có thể có hoặc không thể giải quyết mối quan tâm thực sự của bạn.

Ngay cả trong một số ngôn ngữ không phải là nguyên mẫu, có thể ước tính hiệu ứng này.

Ví dụ, trong Java, một lớp bên trong ẩn danh khá gần với những gì bạn đang mô tả - bạn có thể tạo và khởi tạo một lớp con của bản gốc, chỉ ghi đè phương thức hoặc phương thức bạn muốn. Lớp kết quả sẽ là một instanceoflớp ban đầu, nhưng sẽ không phải là cùng một lớp.

Vì sao bạn muốn làm điều này? Với các biểu thức lambda Java 8, tôi nghĩ rằng nhiều trường hợp sử dụng tốt nhất sẽ biến mất. Với các phiên bản trước của Java, ít nhất, điều này có thể tránh được sự phổ biến của các lớp sử dụng hẹp, tầm thường. Đó là, khi bạn có một số lượng lớn các trường hợp sử dụng liên quan, chỉ khác nhau về một chức năng nhỏ, bạn có thể tạo chúng gần như nhanh chóng (gần như), với sự khác biệt về hành vi được tiêm vào thời điểm bạn cần.

Điều đó nói rằng, ngay cả trước J8, điều này thường có thể được tái cấu trúc để chuyển sự khác biệt thành một hoặc ba trường và đưa chúng vào hàm tạo. Tất nhiên, với J8, phương thức có thể được đưa vào lớp, mặc dù có thể có một sự cám dỗ để làm như vậy khi một phép tái cấu trúc khác có thể sạch hơn (nếu không tuyệt vời).


6

Bạn đã yêu cầu bất kỳ ngôn ngữ nào cung cấp các phương thức theo trường hợp. Đã có câu trả lời cho Javascript, vì vậy hãy xem cách nó được thực hiện trong Common Lisp, nơi bạn có thể sử dụng các chuyên gia 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

Tại sao?

Các chuyên gia EQL rất hữu ích khi đối tượng gửi đi được cho là có một loại có eqlý nghĩa: một số, một ký hiệu, v.v. Nói chung, bạn không cần nó và bạn chỉ cần định nghĩa nhiều lớp con như cần thiết bởi vấn đề của bạn. Nhưng đôi khi, bạn chỉ cần gửi theo một tham số, ví dụ, một ký hiệu: một casebiểu thức sẽ được giới hạn trong các trường hợp đã biết trong hàm gửi, trong khi các phương thức có thể được thêm và xóa bất cứ lúc nào.

Ngoài ra, chuyên môn hóa trong các trường hợp rất hữu ích cho mục đích gỡ lỗi, khi bạn muốn kiểm tra tạm thời những gì xảy ra với một đối tượng cụ thể trong ứng dụng đang chạy của bạn.


5

Bạn cũng có thể làm điều này trong Ruby bằng các đối tượng singleton:

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

Sản xuất:

Hello!
Hello world!

Về phần sử dụng, đây thực sự là cách Ruby thực hiện các phương thức lớp và mô-đun. Ví dụ:

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

Là thực sự xác định một phương thức singleton hellotrên Classđối tượng SomeClass.


Bạn có muốn mở rộng câu trả lời của mình với các mô-đun và phương thức mở rộng không? (Chỉ để hiển thị một số cách khác để mở rộng đối tượng với các phương thức mới)?
knut

@knut: Tôi khá chắc chắn rằng extendthực sự chỉ tạo lớp singleton cho đối tượng và sau đó nhập mô-đun vào lớp singleton.
Linuxios

5

Bạn có thể nghĩ về các phương thức theo trường hợp như cho phép bạn tập hợp lớp của riêng bạn khi chạy. Điều này có thể loại bỏ rất nhiều mã keo, mã đó không có mục đích nào khác ngoài việc đặt hai lớp lại với nhau để nói chuyện với nhau. Mixins là một giải pháp có cấu trúc hơn đối với các loại vấn đề tương tự.

Bạn đang chịu đựng một chút từ nghịch lý trắng trợn , về cơ bản là rất khó để thấy giá trị của một tính năng ngôn ngữ cho đến khi bạn sử dụng tính năng đó trong một chương trình thực. Vì vậy, hãy tìm kiếm những cơ hội mà bạn nghĩ nó có thể hoạt động, thử nó và xem điều gì sẽ xảy ra.

Tìm trong mã của bạn cho các nhóm lớp chỉ khác nhau bởi một phương thức. Tìm các lớp có mục đích duy nhất là kết hợp hai lớp khác trong các kết hợp khác nhau. Tìm kiếm các phương thức không làm gì khác ngoài việc chuyển một cuộc gọi đến một đối tượng khác. Tìm kiếm các lớp được khởi tạo bằng cách sử dụng các mẫu sáng tạo phức tạp . Đó là tất cả các ứng cử viên tiềm năng sẽ được thay thế bằng các phương pháp theo trường hợp.


1

Các câu trả lời khác đã chỉ ra làm thế nào đây là một tính năng phổ biến của các ngôn ngữ hướng đối tượng động và cách nó có thể được mô phỏng một cách tầm thường trong một ngôn ngữ tĩnh có các đối tượng hàm hạng nhất (ví dụ: ủy nhiệm trong c #, các đối tượng ghi đè toán tử () trong c ++) . Trong các ngôn ngữ tĩnh thiếu chức năng như vậy thì khó hơn, nhưng vẫn có thể đạt được bằng cách sử dụng kết hợp mẫu Chiến lược và phương pháp chỉ đơn giản là ủy thác việc thực hiện cho chiến lược. Điều này có hiệu lực giống như những gì bạn đang làm trong c # với các đại biểu, nhưng cú pháp hơi rắc rối hơn.


0

Bạn có thể làm một cái gì đó như thế này trong C # và hầu hết các ngôn ngữ tương tự khá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
Lập trình viên là về câu hỏi khái niệm và câu trả lời dự kiến ​​sẽ giải thích mọi thứ. Ném các đoạn mã thay vì giải thích giống như sao chép mã từ IDE sang bảng trắng: nó có thể trông quen thuộc và thậm chí đôi khi có thể hiểu được, nhưng nó cảm thấy kỳ lạ ... chỉ là lạ. Bảng trắng không có trình biên dịch
gnat

0

Về mặt khái niệm, mặc dù trong một ngôn ngữ như Java, tất cả các phiên bản của một lớp phải có cùng các phương thức, có thể làm cho nó xuất hiện như thể chúng không bằng cách thêm một lớp bổ sung, có thể kết hợp với các lớp lồng nhau.

Ví dụ, nếu một lớp Foocó thể định nghĩa một lớp lồng trừu tượng tĩnh QuackerBasecó chứa một phương thức quack(Foo), cũng như một số lớp lồng nhau tĩnh khác xuất phát từ đó QuackerBase, mỗi lớp có định nghĩa riêng của nó quack(Foo), thì nếu lớp bên ngoài có một trường quackerkiểu QuackerBase, thì nó có thể đặt trường đó để xác định một thể hiện (có thể là đơn lẻ) của bất kỳ một trong các lớp lồng nhau của nó. Sau khi thực hiện xong, việc gọi quacker.quack(this)sẽ thực thi quackphương thức của lớp có cá thể đã được gán cho trường đó.

Vì đây là một mẫu khá phổ biến, Java bao gồm các cơ chế để tự động khai báo các loại thích hợp. Các cơ chế như vậy không thực sự làm bất cứ điều gì không thể thực hiện được bằng cách sử dụng các phương thức ảo và các lớp tĩnh được lồng tùy ý, nhưng chúng làm giảm đáng kể số lượng nồi hơi cần thiết để tạo ra một lớp với mục đích duy nhất là chạy một phương thức duy nhất một lớp khác.


0

Tôi tin rằng đó là định nghĩa của một ngôn ngữ "Động" như ruby, Groovy & Javascript (và Nhiều ngôn ngữ khác). Động đề cập (ít nhất là một phần) cho khả năng tự động xác định lại cách thức một thể hiện của lớp có thể hành xử một cách nhanh chóng.

Nói chung, đây không phải là một thực hành OO tuyệt vời, nhưng đối với nhiều lập trình viên ngôn ngữ năng động, các nguyên tắc OO không phải là ưu tiên hàng đầu của họ.

Nó đơn giản hóa một số thao tác phức tạp như Monkey-Patching trong đó bạn có thể điều chỉnh một thể hiện của lớp để cho phép bạn tương tác với một thư viện đóng theo cách mà họ không thấy trước.


0

Tôi không nói rằng đó là một điều tốt để làm, nhưng điều này là có thể tầm thường trong Python. Tôi không thể là một trường hợp sử dụng tốt ngoài đỉnh đầu của tôi, nhưng tôi chắc chắn rằng chúng tồn tại.

    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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.