Giao diện java tương đương trong Ruby là gì?


101

Chúng ta có thể hiển thị các giao diện trong Ruby như chúng ta làm trong java và thực thi các mô-đun hoặc lớp Ruby để triển khai các phương thức được xác định bởi giao diện không.

Một cách là sử dụng kế thừa và method_missing để đạt được điều tương tự nhưng có cách tiếp cận nào khác thích hợp hơn không?



6
Bạn nên tự hỏi bản thân tại sao Bạn cần điều này. Thường có đủ giao diện được sử dụng chỉ để biên dịch thứ chết tiệt đó không phải là vấn đề đối với ruby.
Arnis Lapsa

1
Câu hỏi này có thể được coi là một bản sao của [ Trong Ruby, tương đương với một giao diện trong C # là gì? ] ( StackOverflow.Com/q/3505521/#3507460 ).
Jörg W Mittag

2
Tại sao tôi cần cái này? Tôi muốn triển khai một cái gì đó mà bạn có thể gọi là "có thể thay đổi được" để làm cho các tài liệu / tệp có thể so sánh được nhưng có thể so sánh được bằng cách sử dụng cái gì .... Ví dụ: tôi có thể làm cho nó có thể thay đổi được bằng cách sử dụng các phần mềm kho lưu trữ hiện có như SVN hoặc CVS. Dù tôi chọn cơ chế nào thì nó cũng phải cung cấp một số chức năng cơ bản tối thiểu. Tôi muốn sử dụng giao diện tương tự để thực thi các chức năng tối thiểu trần này bằng bất kỳ triển khai kho lưu trữ cơ bản mới nào.
crazycrv

Sandi Metz trong cuốn sách POODR của cô ấy sử dụng các bài kiểm tra để ghi lại các giao diện. Thực sự rất đáng để đọc cuốn sách này. Tính đến năm 2015, tôi sẽ nói rằng câu trả lời @ aleksander-pohl là tốt nhất.
Greg Dan

Câu trả lời:


85

Ruby có Giao diện giống như bất kỳ ngôn ngữ nào khác.

Lưu ý rằng bạn phải cẩn thận để không nhầm lẫn khái niệm Giao diện , đó là một đặc tả trừu tượng về trách nhiệm, bảo đảm và giao thức của một đơn vị với khái niệm interfacelà từ khóa trong lập trình Java, C # và VB.NET ngôn ngữ. Trong Ruby, chúng tôi sử dụng cái trước mọi lúc, nhưng cái sau đơn giản là không tồn tại.

Nó là rất quan trọng để phân biệt hai. Điều quan trọng là Giao diện , không phải interface. Nó interfacecho bạn biết khá nhiều điều không hữu ích. Không có gì thể hiện điều này tốt hơn các giao diện đánh dấu trong Java, đó là các giao diện không có thành viên nào cả: chỉ cần xem java.io.Serializablejava.lang.Cloneable; hai chữ interfaceđó có nghĩa là những thứ rất khác nhau, nhưng chúng có cùng một chữ ký.

Vì vậy, nếu hai chữ interfacecái đó có nghĩa là những thứ khác nhau, có cùng một chữ ký, thì điều gì chính xácinterfaceđảm bảo cho bạn?

Một ví dụ điển hình khác:

package java.util;

interface List<E> implements Collection<E>, Iterable<E> {
    void add(int index, E element)
        throws UnsupportedOperationException, ClassCastException,
            NullPointerException, IllegalArgumentException,
            IndexOutOfBoundsException;
}

Là gì Giao diện của java.util.List<E>.add?

  • rằng độ dài của bộ sưu tập không giảm
  • rằng tất cả các mục trong bộ sưu tập trước đó vẫn ở đó
  • đó elementlà trong bộ sưu tập

Và cái nào trong số đó thực sự xuất hiện trong interface? Không ai! Không có gì trong interfaceđó nói rằng Addphương thức thậm chí phải thêm chút nào, nó cũng có thể xóa một phần tử khỏi bộ sưu tập.

Đây là một triển khai hoàn toàn hợp lệ của điều đó interface:

class MyCollection<E> implements java.util.List<E> {
    void add(int index, E element)
        throws UnsupportedOperationException, ClassCastException,
            NullPointerException, IllegalArgumentException,
            IndexOutOfBoundsException {
        remove(element);
    }
}

Một ví dụ khác: java.util.Set<E>nó thực sự nói rằng nó ở đâu, bạn biết đấy, một tập hợp ? Hư không! Hay chính xác hơn là trong tài liệu. Bằng tiếng Anh.

Trong hầu hết các trường hợp interfaces, cả từ Java và .NET, tất cả thông tin liên quan thực sự nằm trong tài liệu chứ không phải trong các loại. Vì vậy, nếu các kiểu không cho bạn biết bất cứ điều gì thú vị, tại sao lại giữ chúng? Tại sao không chỉ vào tài liệu? Và đó chính xác là những gì Ruby làm.

Lưu ý rằng có những ngôn ngữ khácGiao diện thực sự có thể được mô tả theo cách có ý nghĩa. Tuy nhiên, những ngôn ngữ đó thường không gọi cấu trúc mô tả Giao diện " interface", họ gọi nó type. Trong một ngôn ngữ lập trình được định kiểu phụ thuộc, chẳng hạn, bạn có thể diễn đạt các thuộc tính mà một sorthàm trả về một tập hợp có cùng độ dài với tập hợp ban đầu, rằng mọi phần tử trong tập hợp ban đầu cũng nằm trong tập hợp đã sắp xếp và không có phần tử nào lớn hơn xuất hiện trước một phần tử nhỏ hơn.

Vì vậy, nói ngắn gọn: Ruby không tương đương với Java interface. Nó không , tuy nhiên, có một tương đương với một Java Interface , và nó hoàn toàn giống như trong Java: tài liệu hướng dẫn.

Ngoài ra, giống như trong Java, Kiểm tra chấp nhận cũng có thể được sử dụng để chỉ định các Giao diện .

Đặc biệt, trong Ruby, Giao diện của một đối tượng được xác định bởi những gì nó có thể làm , chứ không phải classlà cái gì hay modulenó trộn vào. Bất kỳ đối tượng nào có <<phương thức đều có thể được thêm vào. Điều này rất hữu ích trong các bài kiểm tra đơn vị, nơi bạn có thể chỉ cần vượt qua một Arrayhoặc một Stringthay vì phức tạp hơn Logger, mặc dù ArrayLoggerkhông chia sẻ rõ ràng interfacengoài thực tế là cả hai đều có một phương thức được gọi <<.

Một ví dụ khác là StringIO, mà cụ cùng giao diện như IOvà do đó một phần lớn của giao diện của File, nhưng mà không chia sẻ bất kỳ tổ tiên chung bên cạnh Object.


278
Trong khi một bài đọc hay, tôi không tìm thấy câu trả lời hữu ích. Nó giống như một luận văn về lý do tại sao interfacelà vô ích, thiếu điểm sử dụng của nó. Sẽ dễ dàng hơn khi nói rằng ruby ​​được nhập động và có một trọng tâm khác trong tâm trí và làm cho các khái niệm như IOC trở nên không cần thiết / không mong muốn. Đó là một sự thay đổi khó khăn nếu bạn đã quen với Thiết kế theo Hợp đồng. Rails có thể được hưởng lợi gì đó, mà nhóm cốt lõi đã nhận ra như bạn có thể thấy trên các phiên bản mới nhất.
goliatone

12
Câu hỏi tiếp theo: cách tốt nhất để ghi lại một giao diện trong Ruby là gì? Một từ khóa Java interfacecó thể không cung cấp tất cả thông tin liên quan, nhưng nó cung cấp một nơi rõ ràng để đặt tài liệu. Tôi đã viết một lớp bằng Ruby có triển khai (đủ) IO, nhưng tôi đã làm nó bằng cách thử và sai và không quá hài lòng với quá trình này. Tôi cũng đã viết nhiều cách triển khai một giao diện của riêng mình, nhưng việc ghi lại những phương pháp nào được yêu cầu và những gì chúng phải làm để các thành viên khác trong nhóm của tôi có thể tạo ra các triển khai là một thách thức.
Patrick

9
Cấu interface trúc thực sự chỉ cần thiết để coi các kiểu khác nhau là giống nhau trong các ngôn ngữ thừa kế đơn được nhập tĩnh (ví dụ: coi LinkedHashSetArrayListcả hai là a Collection), nó không liên quan gì nhiều đến Giao diện như câu trả lời này cho thấy. Ruby không được nhập tĩnh nên không cần cấu trúc .
Esailija

16
Tôi đọc điều này là "một số giao diện không có ý nghĩa, do đó giao diện là xấu. Tại sao bạn muốn sử dụng giao diện?". Nó không trả lời câu hỏi và thẳng thắn chỉ nghe như một người không hiểu giao diện dùng để làm gì và lợi ích của chúng.
Oddman

13
Lập luận của bạn về tính không hợp lệ của giao diện Danh sách bằng cách trích dẫn một phương thức thực hiện loại bỏ trong một hàm được gọi là "thêm" là một ví dụ cổ điển về đối số rút gọn quảng cáo. Đặc biệt, bằng bất kỳ ngôn ngữ nào (bao gồm cả ruby) có thể viết một phương thức thực hiện điều gì đó khác với những gì mong đợi. Đây không phải là một đối số hợp lệ chống lại "giao diện" nó chỉ là mã xấu.
Justin Ohms

58

Hãy thử "các ví dụ được chia sẻ" của rspec:

https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples

Bạn viết một thông số kỹ thuật cho giao diện của mình và sau đó đặt một dòng trong thông số kỹ thuật của mỗi người triển khai, ví dụ.

it_behaves_like "my interface"

Toàn bộ ví dụ:

RSpec.shared_examples "a collection" do
  describe "#size" do
    it "returns number of elements" do
      collection = described_class.new([7, 2, 4])
      expect(collection.size).to eq(3)
    end
  end
end

RSpec.describe Array do
  it_behaves_like "a collection"
end

RSpec.describe Set do
  it_behaves_like "a collection"
end

Cập nhật : Tám năm sau (2020) ruby ​​hiện đã hỗ trợ các giao diện được nhập tĩnh qua sorbet. Xem Lớp và Giao diện Tóm tắt trong tài liệu sorbet.


15
Tôi tin rằng đây phải là câu trả lời được chấp nhận. Đây là cách mà hầu hết các loại ngôn ngữ yếu có thể cung cấp các giao diện giống như Java. Điều được chấp nhận giải thích tại sao Ruby không có các giao diện chứ không phải cách mô phỏng chúng.
SystematicFrank,

1
Tôi đồng ý, câu trả lời này đã giúp tôi nhiều hơn với tư cách là một nhà phát triển Java chuyển sang Ruby hơn câu trả lời được chấp nhận ở trên.
Cam

Vâng, nhưng toàn bộ điểm của giao diện là nó có cùng tên phương thức nhưng các lớp cụ thể phải là những lớp thực thi hành vi, có lẽ là khác nhau. Vì vậy, những gì tôi phải thử nghiệm trong ví dụ được chia sẻ?
Rob Wise

Ruby làm cho mọi thứ trở nên thực dụng. Nếu bạn muốn có mã được lập thành văn bản và viết tốt, hãy thêm các bài kiểm tra / thông số kỹ thuật và đó sẽ là loại kiểm tra đánh máy tĩnh.
Dmitry Polushkin

41

Chúng ta có thể hiển thị các giao diện trong Ruby như chúng ta làm trong java và thực thi các mô-đun hoặc lớp Ruby để triển khai các phương thức được xác định bởi giao diện không.

Ruby không có chức năng đó. Về nguyên tắc, nó không cần chúng vì Ruby sử dụng cái gọi là kiểu gõ vịt .

Có một số cách tiếp cận bạn có thể thực hiện.

Viết các triển khai nêu lên các ngoại lệ; nếu một lớp con cố gắng sử dụng phương thức chưa hoàn thành, nó sẽ thất bại

class CollectionInterface
  def add(something)
    raise 'not implemented'
  end
end

Cùng với phần trên, bạn nên viết mã thử nghiệm để thực thi hợp đồng của mình (những gì người khác đăng ở đây gọi là Giao diện không chính xác )

Nếu bạn thấy mình luôn viết các phương thức void như trên, thì hãy viết một mô-đun trợ giúp nắm bắt điều đó

module Interface
  def method(name)
    define_method(name) { |*args|
      raise "interface method #{name} not implemented"
    }
  end
end

class Collection
  extend Interface
  method :add
  method :remove
end

Bây giờ, kết hợp những điều trên với các mô-đun Ruby và bạn đã gần đạt được những gì mình muốn ...

module Interface
  def method(name)
    define_method(name) { |*args|
      raise "interface method #{name} not implemented"
    }
  end
end

module Collection
  extend Interface
  method :add
  method :remove
end

col = Collection.new # <-- fails, as it should

Và sau đó bạn có thể làm

class MyCollection
  include Collection

  def add(thing)
    puts "Adding #{thing}"
  end
end

c1 = MyCollection.new
c1.add(1)     # <-- output 'Adding 1'
c1.remove(1)  # <-- fails with not implemented

Hãy để tôi nhấn mạnh lại một lần nữa: đây là một điều thô sơ, vì mọi thứ trong Ruby đều xảy ra trong thời gian chạy; không có kiểm tra thời gian biên dịch. Nếu bạn kết hợp điều này với thử nghiệm, thì bạn sẽ có thể nhận ra lỗi. Thậm chí xa hơn, nếu bạn tiến xa hơn ở trên, bạn có thể có thể viết một Giao diện thực hiện kiểm tra trên lớp lần đầu tiên một đối tượng của lớp đó được tạo; làm cho các bài kiểm tra của bạn đơn giản như gọi MyCollection.new... yeah, over the top :)


Được nhưng nếu Collection = MyCollection của bạn triển khai một phương thức không được xác định trong Giao diện, thì phương thức này hoạt động hoàn hảo, vì vậy bạn không thể đảm bảo Đối tượng của mình chỉ có định nghĩa các phương thức Giao diện.
Joel AZEMAR

Điều này là khá tuyệt vời, cảm ơn. Gõ vịt là tốt, nhưng đôi khi tốt hơn khi giao tiếp rõ ràng với các nhà phát triển khác về cách một giao diện sẽ hoạt động.
Mirodinho

10

Như mọi người ở đây đã nói, không có hệ thống giao diện cho ruby. Nhưng thông qua việc xem xét nội tâm, bạn có thể tự mình thực hiện nó khá dễ dàng. Dưới đây là một ví dụ đơn giản có thể được cải thiện theo nhiều cách để giúp bạn bắt đầu:

class Object
  def interface(method_hash)
    obj = new
    method_hash.each do |k,v|
      if !obj.respond_to?(k) || !((instance_method(k).arity+1)*-1)
        raise NotImplementedError, "#{obj.class} must implement the method #{k} receiving #{v} parameters"
      end
    end
  end
end

class Person
  def work(one,two,three)
    one + two + three
  end

  def sleep
  end

  interface({:work => 3, :sleep => 0})
end

Loại bỏ một trong các phương thức được khai báo trên Person hoặc thay đổi nó, số lượng đối số sẽ tăng a NotImplementedError.


5

Không có những thứ gọi là giao diện theo cách Java. Nhưng có những thứ khác bạn có thể thưởng thức trong ruby.

Nếu bạn muốn triển khai một số loại và giao diện - để các đối tượng có thể được kiểm tra xem chúng có một số phương thức / thông báo mà bạn yêu cầu từ chúng hay không - thì bạn có thể xem qua các hợp đồng ruby . Nó xác định một cơ chế tương tự như PyProtocols . Một blog về loại kiểm tra trong ruby ​​là ở đây .

Các dự án được tiếp cận được đề cập không phải là các dự án đang sống, mặc dù mục tiêu lúc đầu có vẻ tốt đẹp, nhưng có vẻ như hầu hết các nhà phát triển ruby ​​có thể sống mà không cần kiểm tra loại nghiêm ngặt. Nhưng tính linh hoạt của ruby ​​cho phép thực hiện kiểm tra kiểu.

Nếu bạn muốn mở rộng các đối tượng hoặc lớp (cùng một thứ trong ruby) bằng các hành vi nhất định hoặc phần nào có cách đa kế thừa của ruby, hãy sử dụng cơ chế includehoặc extend. Với includebạn có thể bao gồm các phương thức từ một lớp hoặc mô-đun khác vào một đối tượng. Với việc extendbạn có thể thêm hành vi vào một lớp, để các thể hiện của nó sẽ có thêm các phương thức. Đó là một lời giải thích rất ngắn gọn.

Theo tôi, cách tốt nhất để giải quyết nhu cầu về giao diện Java là hiểu mô hình đối tượng ruby ​​(xem bài giảng của Dave Thomas chẳng hạn). Có lẽ bạn sẽ quên giao diện Java. Hoặc bạn có một ứng dụng đặc biệt trong lịch trình của mình.


Những bài giảng của Dave Thomas nằm sau một bức tường phí.
Purplejacket

5

Như nhiều câu trả lời đã chỉ ra, trong Ruby không có cách nào để buộc một lớp thực hiện một phương thức cụ thể, bằng cách kế thừa từ một lớp, bao gồm một mô-đun hoặc bất kỳ thứ gì tương tự. Lý do cho điều đó có lẽ là sự phổ biến của TDD trong cộng đồng Ruby, đó là một cách khác để xác định giao diện - các bài kiểm tra không chỉ xác định chữ ký của các phương thức mà còn cả hành vi. Vì vậy, nếu bạn muốn triển khai một lớp khác, thực hiện một số giao diện đã được xác định, bạn phải đảm bảo rằng tất cả các bài kiểm tra đều vượt qua.

Thông thường các bài kiểm tra được xác định một cách riêng biệt bằng cách sử dụng mocks và sơ khai. Nhưng cũng có những công cụ như Bogus , cho phép xác định các bài kiểm tra hợp đồng. Các bài kiểm tra như vậy không chỉ xác định hành vi của lớp "chính" mà còn kiểm tra xem các phương thức sơ khai có tồn tại trong các lớp hợp tác hay không.

Nếu bạn thực sự quan tâm đến các giao diện trong Ruby, tôi khuyên bạn nên sử dụng một khung thử nghiệm thực hiện thử nghiệm hợp đồng.


3

Tất cả các ví dụ ở đây đều thú vị nhưng thiếu xác thực của hợp đồng Giao diện, ý tôi là nếu bạn muốn đối tượng của mình triển khai tất cả định nghĩa phương thức Giao diện và chỉ những phương thức này thì bạn không thể. Vì vậy, tôi đề xuất cho bạn một ví dụ đơn giản nhanh chóng (chắc chắn có thể được cải thiện) để đảm bảo bạn có chính xác những gì bạn mong đợi thông qua Giao diện của mình (Hợp đồng).

xem xét Giao diện của bạn với các phương pháp đã xác định như vậy

class FooInterface
  class NotDefinedMethod < StandardError; end
  REQUIRED_METHODS = %i(foo).freeze
  def initialize(object)
    @object = object
    ensure_method_are_defined!
  end
  def method_missing(method, *args, &block)
    ensure_asking_for_defined_method!(method)
    @object.public_send(method, *args, &block)
  end
  private
  def ensure_method_are_defined!
    REQUIRED_METHODS.each do |method|
      if !@object.respond_to?(method)
        raise NotImplementedError, "#{@object.class} must implement the method #{method}"
      end
    end
  end
  def ensure_asking_for_defined_method!(method)
    unless REQUIRED_METHODS.include?(method)
      raise NotDefinedMethod, "#{method} doesn't belong to Interface definition"
    end
  end
end

Sau đó, bạn có thể viết một đối tượng với ít nhất là hợp đồng Giao diện:

class FooImplementation
  def foo
    puts('foo')
  end
  def bar
    puts('bar')
  end
end

Bạn có thể gọi Đối tượng của mình một cách an toàn thông qua Giao diện để đảm bảo bạn là chính xác những gì Giao diện xác định

#  > FooInterface.new(FooImplementation.new).foo
# => foo

#  > FooInterface.new(FooImplementation.new).bar
# => FooInterface::NotDefinedMethod: bar doesn't belong to Interface definition

Và bạn cũng có thể đảm bảo Đối tượng của bạn triển khai tất cả các định nghĩa phương thức Giao diện của bạn

class BadFooImplementation
end

#  > FooInterface.new(BadFooImplementation.new)
# => NotImplementedError: BadFooImplementation must implement the method foo

2

Tôi đã mở rộng một chút về câu trả lời của carlosayam cho các nhu cầu bổ sung của tôi. Điều này bổ sung một số thực thi và tùy chọn bổ sung cho lớp Giao diện: required_variableoptional_variablehỗ trợ giá trị mặc định.

Tôi không chắc rằng bạn sẽ muốn sử dụng lập trình meta này với một thứ gì đó quá lớn.

Như các câu trả lời khác đã nêu, tốt nhất bạn nên viết các bài kiểm tra thực thi đúng những gì bạn đang tìm kiếm, đặc biệt khi bạn muốn bắt đầu thực thi các tham số và trả về giá trị.

Cảnh báo phương pháp này chỉ tạo ra một lỗi khi gọi mã. Các thử nghiệm sẽ vẫn được yêu cầu để thực thi thích hợp trước thời gian chạy.

Ví dụ về mã

interface.rb

module Interface
  def method(name)
    define_method(name) do
      raise "Interface method #{name} not implemented"
    end
  end

  def required_variable(name)
    define_method(name) do
      sub_class_var = instance_variable_get("@#{name}")
      throw "@#{name} must be defined" unless sub_class_var
      sub_class_var
    end
  end

  def optional_variable(name, default)
    define_method(name) do
      instance_variable_get("@#{name}") || default
    end
  end
end

plugin.rb

Tôi đã sử dụng thư viện singleton cho mẫu đã cho mà tôi đang sử dụng. Bằng cách này, bất kỳ lớp con nào cũng kế thừa thư viện singleton khi triển khai "giao diện" này.

require 'singleton'

class Plugin
  include Singleton

  class << self
    extend Interface

    required_variable(:name)
    required_variable(:description)
    optional_variable(:safe, false)
    optional_variable(:dependencies, [])

    method :run
  end
end

my_plugin.rb

Đối với nhu cầu của tôi, điều này yêu cầu lớp thực hiện giao diện "" phân lớp.

class MyPlugin < Plugin

  @name = 'My Plugin'
  @description = 'I am a plugin'
  @safe = true

  def self.run
    puts 'Do Stuff™'
  end
end

2

Bản thân Ruby không có tương đương chính xác với các giao diện trong Java.

Tuy nhiên, vì một giao diện như vậy đôi khi có thể rất hữu ích, tôi đã tự phát triển một viên ngọc cho Ruby, nó mô phỏng các giao diện Java một cách rất đơn giản.

Nó được gọi là class_interface.

Nó hoạt động khá đơn giản. Đầu tiên hãy cài đặt gem bằng cách gem install class_interfacehoặc thêm nó vào Gemfile và rund của bạn bundle install.

Xác định giao diện:

require 'class_interface'

class IExample
  MIN_AGE = Integer
  DEFAULT_ENV = String
  SOME_CONSTANT = nil

  def self.some_static_method
  end

  def some_instance_method
  end
end

Triển khai giao diện đó:

class MyImplementation
  MIN_AGE = 21
  DEFAULT_ENV = 'dev' 
  SOME_CONSTANT = 'some_value'

  def specific_method
    puts "very specific"
  end

  def self.some_static_method
    puts "static method is implemented!"
  end

  def some_instance_method
    # implementation
  end

  def self.another_methods
    # implementation
  end

  implements IExample
end

Nếu bạn không triển khai một hằng số hoặc phương thức nhất định hoặc số tham số không khớp, một lỗi tương ứng sẽ xuất hiện trước khi chương trình Ruby được thực thi. Bạn thậm chí có thể xác định kiểu của hằng số bằng cách gán một kiểu trong giao diện. Nếu không, bất kỳ loại nào cũng được phép.

Phương thức "thực hiện" phải được gọi ở dòng cuối cùng của một lớp, vì đó là vị trí mã nơi các phương thức được triển khai ở trên đã được kiểm tra.

Xem thêm tại: https://github.com/magynhard/class_interface


0

Tôi nhận ra rằng tôi đang sử dụng quá nhiều mẫu "Lỗi không được triển khai" để kiểm tra độ an toàn trên các đối tượng mà tôi muốn có một hành vi cụ thể. Kết thúc việc viết một viên ngọc về cơ bản cho phép sử dụng một giao diện như thế này:

require 'playable' 

class Instrument 
  implements Playable
end

Instrument.new #will throw: Interface::Error::NotImplementedError: Expected Instrument to implement play for interface Playable

Nó không kiểm tra các đối số của phương thức . Nó không giống như phiên bản 0.2.0. Ví dụ chi tiết hơn tại https://github.com/bluegod/rint

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.