Bạn có thể sử dụng bản sao để lập trình dựa trên nguyên mẫu trong Ruby. Lớp Object của Ruby định nghĩa cả phương thức clone và phương thức dup. Cả clone và dup đều tạo ra một bản sao nông của đối tượng mà nó đang sao chép; nghĩa là, các biến đối tượng của đối tượng được sao chép nhưng không phải là các đối tượng mà chúng tham chiếu. Tôi sẽ chứng minh một ví dụ:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color << ' orange'
=> "red orange"
apple.color
=> "red orange"
Lưu ý trong ví dụ trên, bản sao màu cam sao chép trạng thái (nghĩa là các biến thể hiện) của đối tượng apple, nhưng trong đó đối tượng apple tham chiếu các đối tượng khác (như màu đối tượng String), các tham chiếu đó không được sao chép. Thay vào đó, táo và cam đều tham chiếu cùng một đối tượng! Trong ví dụ của chúng tôi, tham chiếu là đối tượng chuỗi 'đỏ'. Khi màu cam sử dụng phương thức chắp thêm, <<, để sửa đổi đối tượng Chuỗi hiện có, nó sẽ thay đổi đối tượng chuỗi thành 'màu đỏ cam'. Điều này cũng có hiệu lực thay đổi apple.color, vì cả hai đều trỏ đến cùng một đối tượng String.
Là một lưu ý phụ, toán tử gán, =, sẽ gán một đối tượng mới và do đó hủy một tham chiếu. Đây là một minh chứng:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color = 'orange'
orange.color
=> 'orange'
apple.color
=> 'red'
Trong ví dụ trên, khi chúng ta đã gán một đối tượng mới cho phương thức thể hiện màu của bản sao màu cam, nó không còn tham chiếu cùng một đối tượng như apple. Do đó, bây giờ chúng ta có thể sửa đổi phương thức màu của màu cam mà không ảnh hưởng đến phương thức màu của quả táo, nhưng nếu chúng ta sao chép một đối tượng khác từ quả táo, thì đối tượng mới đó sẽ tham chiếu cùng một đối tượng trong các biến đối tượng được sao chép là táo.
dup cũng sẽ tạo ra một bản sao nông của đối tượng mà nó đang sao chép và nếu bạn thực hiện cùng một minh họa được hiển thị ở trên để dup, bạn sẽ thấy nó hoạt động chính xác theo cách tương tự. Nhưng có hai sự khác biệt chính giữa clone và dup. Đầu tiên, như những người khác đã đề cập, sao chép trạng thái đóng băng và dup không. Điều đó có nghĩa là gì? Thuật ngữ 'đóng băng' trong Ruby là một thuật ngữ bí truyền cho sự bất biến, bản thân nó là một danh pháp trong khoa học máy tính, có nghĩa là một cái gì đó không thể thay đổi. Do đó, một đối tượng bị đóng băng trong Ruby không thể được sửa đổi theo bất kỳ cách nào; đó là, trong thực tế, bất biến. Nếu bạn cố gắng sửa đổi một đối tượng bị đóng băng, Ruby sẽ đưa ra một ngoại lệ RuntimeError. Vì bản sao sao chép trạng thái đóng băng, nếu bạn cố gắng sửa đổi một đối tượng nhân bản, nó sẽ đưa ra một ngoại lệ RuntimeError. Ngược lại, vì dup không sao chép trạng thái đóng băng,
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.frozen?
=> false
apple.freeze
apple.frozen?
=> true
apple.color = 'crimson'
RuntimeError: can't modify frozen Apple
apple.color << ' crimson'
=> "red crimson" # we cannot modify the state of the object, but we can certainly modify objects it is referencing!
orange = apple.dup
orange.frozen?
=> false
orange2 = apple.clone
orange2.frozen?
=> true
orange.color = 'orange'
=> "orange" # we can modify the orange object since we used dup, which did not copy the frozen state
orange2.color = 'orange'
RuntimeError: can't modify frozen Apple # orange2 raises an exception since the frozen state was copied via clone
Thứ hai, và, thú vị hơn, sao chép bản sao lớp đơn (và do đó phương thức của nó)! Điều này rất hữu ích nếu bạn muốn thực hiện lập trình dựa trên nguyên mẫu trong Ruby. Đầu tiên, chúng ta hãy chỉ ra rằng các phương thức singleton được sao chép bằng bản sao và sau đó chúng ta có thể áp dụng nó trong một ví dụ về lập trình dựa trên nguyên mẫu trong Ruby.
class Fruit
attr_accessor :origin
def initialize
@origin = :plant
end
end
fruit = Fruit.new
=> #<Fruit:0x007fc9e2a49260 @origin=:plant>
def fruit.seeded?
true
end
2.4.1 :013 > fruit.singleton_methods
=> [:seeded?]
apple = fruit.clone
=> #<Fruit:0x007fc9e2a19a10 @origin=:plant>
apple.seeded?
=> true
Như bạn có thể thấy, lớp singleton của thể hiện đối tượng trái cây được sao chép vào bản sao. Và do đó, đối tượng nhân bản có quyền truy cập vào phương thức singleton: seeded?. Nhưng đây không phải là trường hợp với dup:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
Bây giờ trong lập trình dựa trên nguyên mẫu, bạn không có các lớp mở rộng các lớp khác và sau đó tạo các thể hiện của các lớp có phương thức xuất phát từ lớp cha đóng vai trò là một kế hoạch chi tiết. Thay vào đó, bạn có một đối tượng cơ sở và sau đó bạn tạo một đối tượng mới từ đối tượng với các phương thức và trạng thái của nó được sao chép (tất nhiên, vì chúng ta đang thực hiện các bản sao nông thông qua bản sao, mọi đối tượng tham chiếu biến đối tượng sẽ được chia sẻ giống như trong JavaScript nguyên mẫu). Sau đó, bạn có thể điền hoặc thay đổi trạng thái của đối tượng bằng cách điền vào các chi tiết của các phương thức nhân bản. Trong ví dụ dưới đây, chúng ta có một đối tượng trái cây cơ sở. Tất cả trái cây đều có hạt, vì vậy chúng tôi tạo ra một phương thức number_of_seệt. Nhưng táo có một hạt giống, và vì vậy chúng tôi tạo một bản sao và điền vào các chi tiết. Bây giờ khi chúng tôi nhân bản táo, chúng tôi không chỉ nhân bản các phương thức mà chúng tôi còn nhân bản tiểu bang! Ghi nhớ bản sao làm một bản sao nông của trạng thái (biến thể hiện). Và vì điều đó, khi chúng tôi nhân bản táo để lấy red_apple, red_apple sẽ tự động có 1 hạt giống! Bạn có thể nghĩ red_apple là một đối tượng kế thừa từ Apple, từ đó thừa hưởng từ Fruit. Do đó, đó là lý do tại sao tôi viết hoa trái cây và táo. Chúng tôi đã đi xa với sự phân biệt giữa các lớp và các đối tượng lịch sự của bản sao.
Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
@number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
@number_of_seeds
end
Apple = Fruit.clone
=> #<Object:0x007fb1d78165d8>
Apple.number_of_seeds = 1
Apple.number_of_seeds
=> 1
red_apple = Apple.clone
=> #<Object:0x007fb1d892ac20 @number_of_seeds=1>
red_apple.number_of_seeds
=> 1
Tất nhiên, chúng ta có thể có một phương thức constructor trong lập trình dựa trên protoype:
Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
@number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
@number_of_seeds
end
def Fruit.init(number_of_seeds)
fruit_clone = clone
fruit_clone.number_of_seeds = number_of_seeds
fruit_clone
end
Apple = Fruit.init(1)
=> #<Object:0x007fcd2a137f78 @number_of_seeds=1>
red_apple = Apple.clone
=> #<Object:0x007fcd2a1271c8 @number_of_seeds=1>
red_apple.number_of_seeds
=> 1
Cuối cùng, bằng cách sử dụng bản sao, bạn có thể nhận được một cái gì đó tương tự như hành vi nguyên mẫu JavaScript.
dup
vàclone
làm gì , mà tại sao bạn lại sử dụng cái này chứ không phải cái kia.