Tôi đang học cách tạo OOP bằng JavaScript . Nó có khái niệm giao diện (như Java interface
) không?
Vì vậy, tôi sẽ có thể tạo ra một người nghe ...
Tôi đang học cách tạo OOP bằng JavaScript . Nó có khái niệm giao diện (như Java interface
) không?
Vì vậy, tôi sẽ có thể tạo ra một người nghe ...
Câu trả lời:
Không có khái niệm "lớp này phải có các chức năng này" (nghĩa là không có giao diện nào), bởi vì:
Thay vào đó, JavaScript sử dụng cái được gọi là gõ vịt . (Nếu nó đi như một con vịt và quạ giống như một con vịt, theo như JS quan tâm, thì đó là một con vịt.) Nếu đối tượng của bạn có các phương thức quack (), walk () và fly (), mã có thể sử dụng nó bất cứ nơi nào nó mong đợi một đối tượng có thể đi bộ, lang băm và bay, mà không yêu cầu thực hiện một số giao diện "Duckable". Giao diện chính xác là tập hợp các hàm mà mã sử dụng (và các giá trị trả về từ các hàm đó) và với cách gõ vịt, bạn sẽ có được điều đó miễn phí.
Bây giờ, điều đó không có nghĩa là mã của bạn sẽ không bị lỗi giữa chừng, nếu bạn cố gắng gọi some_dog.quack()
; bạn sẽ nhận được TypeError. Thành thật mà nói, nếu bạn bảo chó đi lang thang, bạn có vấn đề lớn hơn một chút; Gõ vịt hoạt động tốt nhất khi bạn giữ tất cả các con vịt của bạn liên tiếp, có thể nói, và không để chó và vịt hòa nhập với nhau trừ khi bạn coi chúng là động vật chung. Nói cách khác, mặc dù giao diện trôi chảy, nó vẫn ở đó; thường là một lỗi khi chuyển một con chó sang mã mà hy vọng nó sẽ quẫy và bay ngay từ đầu.
Nhưng nếu bạn chắc chắn rằng mình đang làm đúng, bạn có thể giải quyết vấn đề về con chó bằng cách kiểm tra sự tồn tại của một phương pháp cụ thể trước khi thử sử dụng nó. Cái gì đó như
if (typeof(someObject.quack) == "function")
{
// This thing can quack
}
Vì vậy, bạn có thể kiểm tra tất cả các phương pháp bạn có thể sử dụng trước khi sử dụng chúng. Cú pháp là loại xấu, mặc dù. Có một cách hay hơn một chút:
Object.prototype.can = function(methodName)
{
return ((typeof this[methodName]) == "function");
};
if (someObject.can("quack"))
{
someObject.quack();
}
Đây là JavaScript tiêu chuẩn, do đó, nó sẽ hoạt động trong bất kỳ trình thông dịch JS nào đáng sử dụng. Nó có thêm lợi ích của việc đọc như tiếng Anh.
Đối với các trình duyệt hiện đại (nghĩa là, hầu như bất kỳ trình duyệt nào khác ngoài IE 6-8), thậm chí còn có một cách để giữ cho tài sản không hiển thị trong for...in
:
Object.defineProperty(Object.prototype, 'can', {
enumerable: false,
value: function(method) {
return (typeof this[method] === 'function');
}
}
Vấn đề là các đối tượng IE7 hoàn toàn không có .defineProperty
, và trong IE8, nó được cho là chỉ hoạt động trên các đối tượng máy chủ (nghĩa là các phần tử DOM, v.v.). Nếu khả năng tương thích là một vấn đề, bạn không thể sử dụng .defineProperty
. (Tôi thậm chí sẽ không đề cập đến IE6, vì nó không còn liên quan nữa bên ngoài Trung Quốc.)
Một vấn đề khác là một số phong cách mã hóa muốn cho rằng mọi người đều viết mã xấu và cấm sửa đổi Object.prototype
trong trường hợp ai đó muốn sử dụng một cách mù quáng for...in
. Nếu bạn quan tâm đến điều đó hoặc đang sử dụng mã (IMO bị hỏng ), hãy thử một phiên bản hơi khác:
function can(obj, methodName)
{
return ((typeof obj[methodName]) == "function");
}
if (can(someObject, "quack"))
{
someObject.quack();
}
for...in
là - và luôn luôn - đầy rẫy những nguy hiểm như vậy, và bất cứ ai làm điều đó mà không ít nhất là xem xét rằng ai đó đã thêm vào Object.prototype
(một kỹ thuật không phổ biến, bởi sự thừa nhận của chính bài báo đó) sẽ thấy mã của họ bị phá vỡ trong tay người khác.
for...in
vấn đề. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
for...in
vấn đề" vẫn sẽ tồn tại ở một mức độ nào đó, bởi vì sẽ luôn có mã cẩu thả ... tốt, điều đó, và Object.defineProperty(obj, 'a', {writable: true, enumerable: false, value: 3});
còn khá nhiều công việc hơn chỉ là obj.a = 3;
. Tôi hoàn toàn có thể hiểu mọi người không cố gắng làm điều đó thường xuyên hơn. : P
Chọn một bản sao ' Mẫu thiết kế JavaScript ' của Dustin Diaz . Có một vài chương dành riêng để thực hiện giao diện JavaScript thông qua Duck Typing. Đó là một đọc tốt đẹp là tốt. Nhưng không, không có ngôn ngữ thực thi giao diện gốc, bạn phải sử dụng Duck Type .
// example duck typing method
var hasMethods = function(obj /*, method list as strings */){
var i = 1, methodName;
while((methodName = arguments[i++])){
if(typeof obj[methodName] != 'function') {
return false;
}
}
return true;
}
// in your code
if(hasMethods(obj, 'quak', 'flapWings','waggle')) {
// IT'S A DUCK, do your duck thang
}
JavaScript (ECMAScript phiên bản 3) có một implements
từ dành riêng được lưu lại để sử dụng trong tương lai . Tôi nghĩ rằng điều này được dự định chính xác cho mục đích này, tuy nhiên, trong lúc vội vã đưa thông số kỹ thuật ra khỏi cửa, họ không có thời gian để xác định phải làm gì với nó, vì vậy, tại thời điểm hiện tại, các trình duyệt không làm gì ngoài hãy để nó ngồi đó và thỉnh thoảng phàn nàn nếu bạn cố gắng sử dụng nó cho một cái gì đó.
Có thể và thực sự đủ dễ dàng để tạo Object.implement(Interface)
phương thức của riêng bạn với logic mà baulks mỗi khi một tập hợp các thuộc tính / hàm cụ thể không được triển khai trong một đối tượng nhất định.
Tôi đã viết một bài viết về hướng đối tượng trong đó sử dụng ký hiệu của riêng tôi như sau :
// Create a 'Dog' class that inherits from 'Animal'
// and implements the 'Mammal' interface
var Dog = Object.extend(Animal, {
constructor: function(name) {
Dog.superClass.call(this, name);
},
bark: function() {
alert('woof');
}
}).implement(Mammal);
Có nhiều cách để lột con mèo đặc biệt này, nhưng đây là logic tôi đã sử dụng để thực hiện Giao diện của riêng mình. Tôi thấy tôi thích cách tiếp cận này, và nó rất dễ đọc và sử dụng (như bạn có thể thấy ở trên). Điều đó có nghĩa là thêm một phương thức 'hiện thực' Function.prototype
mà một số người có thể gặp vấn đề, nhưng tôi thấy nó hoạt động rất tốt.
Function.prototype.implement = function() {
// Loop through each interface passed in and then check
// that its members are implemented in the context object (this).
for(var i = 0; i < arguments.length; i++) {
// .. Check member's logic ..
}
// Remember to return the class being tested
return this;
}
var interf = arguments[i]; for (prop in interf) { if (this.prototype[prop] === undefined) { throw 'Member [' + prop + '] missing from class definition.'; }}
. Xem dưới cùng của liên kết bài viết cho một ví dụ chi tiết hơn.
Mặc dù JavaScript không có interface
loại, nhưng nó thường cần thời gian. Vì các lý do liên quan đến bản chất động của JavaScript và việc sử dụng Nguyên mẫu-Kế thừa, rất khó để đảm bảo các giao diện nhất quán giữa các lớp - tuy nhiên, có thể làm như vậy; và thường xuyên thi đua.
Tại thời điểm này, có một số cách cụ thể để mô phỏng Giao diện trong JavaScript; phương sai trong các phương pháp thường thỏa mãn một số nhu cầu, trong khi những phương pháp khác không được giải quyết. Thông thường, cách tiếp cận mạnh mẽ nhất là quá cồng kềnh và cản trở người thực hiện (nhà phát triển).
Đây là một cách tiếp cận với Giao diện / Các lớp trừu tượng không quá cồng kềnh, mang tính khám phá, giữ cho việc triển khai bên trong Trừu tượng đến mức tối thiểu và để đủ chỗ cho các phương pháp động hoặc tùy chỉnh:
function resolvePrecept(interfaceName) {
var interfaceName = interfaceName;
return function curry(value) {
/* throw new Error(interfaceName + ' requires an implementation for ...'); */
console.warn('%s requires an implementation for ...', interfaceName);
return value;
};
}
var iAbstractClass = function AbstractClass() {
var defaultTo = resolvePrecept('iAbstractClass');
this.datum1 = this.datum1 || defaultTo(new Number());
this.datum2 = this.datum2 || defaultTo(new String());
this.method1 = this.method1 || defaultTo(new Function('return new Boolean();'));
this.method2 = this.method2 || defaultTo(new Function('return new Object();'));
};
var ConcreteImplementation = function ConcreteImplementation() {
this.datum1 = 1;
this.datum2 = 'str';
this.method1 = function method1() {
return true;
};
this.method2 = function method2() {
return {};
};
//Applies Interface (Implement iAbstractClass Interface)
iAbstractClass.apply(this); // .call / .apply after precept definitions
};
Giải quyết giới
Các resolvePrecept
chức năng là một tiện ích & helper chức năng để sử dụng bên trong của Abstract class . Công việc của nó là cho phép thực hiện tùy chỉnh - xử lý các Giới luật được đóng gói (dữ liệu & hành vi) . Nó có thể đưa ra lỗi hoặc cảnh báo - VÀ - gán giá trị mặc định cho lớp Người triển khai.
i sát thương
Các iAbstractClass
định nghĩa giao diện sẽ được sử dụng. Cách tiếp cận của nó đòi hỏi một thỏa thuận ngầm với lớp Người thực hiện. Giao diện này gán từng giới cho cùng một không gian tên chính xác - HOẶC - cho bất cứ điều gì mà hàm Precolver trả về. Tuy nhiên, thỏa thuận ngầm giải quyết theo bối cảnh - một điều khoản của Người thực hiện.
Người thực hiện
Implementor chỉ đơn giản là 'đồng ý' với một giao diện ( iAbstractClass trong trường hợp này) và áp dụng nó bằng cách sử dụng Constructor-Hijacking : iAbstractClass.apply(this)
. Bằng cách xác định dữ liệu & hành vi ở trên, sau đó chiếm quyền điều khiển của Trình tạo giao diện - chuyển ngữ cảnh của Người triển khai đến Trình tạo giao diện - chúng tôi có thể đảm bảo rằng phần ghi đè của Người triển khai sẽ được thêm vào và Giao diện sẽ giải thích các cảnh báo và giá trị mặc định.
Đây là một cách tiếp cận không cồng kềnh đã phục vụ nhóm của tôi và tôi rất tốt cho quá trình thời gian và các dự án khác nhau. Tuy nhiên, nó có một số cảnh báo và nhược điểm.
Hạn chế
Mặc dù điều này giúp triển khai tính nhất quán trong toàn bộ phần mềm của bạn ở một mức độ đáng kể, nhưng nó không thực hiện các giao diện thực sự - nhưng mô phỏng chúng. Mặc dù các định nghĩa, mặc định và cảnh báo hoặc lỗi được giải thích, việc khám phá sử dụng được nhà phát triển thực thi & khẳng định (cũng như phần lớn sự phát triển JavaScript).
Đây dường như là cách tiếp cận tốt nhất cho "Giao diện trong JavaScript" , tuy nhiên, tôi rất muốn thấy các giải pháp sau:
delete
hành độngĐiều đó nói rằng, tôi hy vọng điều này sẽ giúp bạn nhiều như nó có đội của tôi và tôi.
Bạn cần các giao diện trong Java vì nó được gõ tĩnh và hợp đồng giữa các lớp nên được biết trong quá trình biên dịch. Trong JavaScript thì khác. JavaScript được gõ động; điều đó có nghĩa là khi bạn nhận được đối tượng, bạn có thể kiểm tra xem nó có phương thức cụ thể nào không và gọi nó.
yourMethod
ở mục số 5 trong Superclass
vtable của nó và đối với mỗi lớp con có riêng nó yourMethod
, chỉ cần chỉ ra mục nhập của lớp con # 5 lúc thực hiện phù hợp.
Implementation
thực SomeInterface
hiện không chỉ nói rằng nó thực hiện toàn bộ giao diện. Nó có thông tin cho biết "Tôi triển khai SomeInterface.yourMethod
" và chỉ ra định nghĩa phương thức cho Implementation.yourMethod
. Khi JVM gọi SomeInterface.yourMethod
, nó tìm trong lớp để biết thông tin về việc triển khai phương thức của giao diện đó và thấy nó cần gọi Implementation.yourMethod
.
Hy vọng rằng bất cứ ai vẫn đang tìm kiếm một câu trả lời đều thấy nó hữu ích.
Bạn có thể dùng thử Proxy (Đó là tiêu chuẩn kể từ ECMAScript 2015): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
latLngLiteral = new Proxy({},{
set: function(obj, prop, val) {
//only these two properties can be set
if(['lng','lat'].indexOf(prop) == -1) {
throw new ReferenceError('Key must be "lat" or "lng"!');
}
//the dec format only accepts numbers
if(typeof val !== 'number') {
throw new TypeError('Value must be numeric');
}
//latitude is in range between 0 and 90
if(prop == 'lat' && !(0 < val && val < 90)) {
throw new RangeError('Position is out of range!');
}
//longitude is in range between 0 and 180
else if(prop == 'lng' && !(0 < val && val < 180)) {
throw new RangeError('Position is out of range!');
}
obj[prop] = val;
return true;
}
});
Sau đó, bạn có thể dễ dàng nói:
myMap = {}
myMap.position = latLngLiteral;
Khi bạn muốn sử dụng một trình biên dịch, thì bạn có thể dùng thử TypeScript. Nó hỗ trợ dự thảo các tính năng ECMA (trong đề xuất, các giao diện được gọi là " giao thức ") tương tự như các ngôn ngữ như coffeescript hoặc babel làm.
Trong TypeScript, giao diện của bạn có thể trông như sau:
interface IMyInterface {
id: number; // TypeScript types are lowercase
name: string;
callback: (key: string; value: any; array: string[]) => void;
type: "test" | "notATest"; // so called "union type"
}
Những gì bạn không thể làm:
không có giao diện gốc trong JavaScript, có một số cách để mô phỏng giao diện. tôi đã viết một gói mà làm điều đó
bạn có thể thấy cấy ghép ở đây
Javascript không có giao diện. Nhưng nó có thể được gõ vịt, một ví dụ có thể được tìm thấy ở đây:
http://reinsbrain.blogspot.com/2008/10/interface-in-javascript.html
Tôi biết đây là một cái cũ, nhưng gần đây tôi thấy mình cần nhiều hơn nữa để có một API tiện dụng để kiểm tra các đối tượng dựa trên các giao diện. Vì vậy, tôi đã viết điều này: https://github.com/tomhicks/methodical
Nó cũng có sẵn thông qua NPM: npm install methodical
Về cơ bản, nó thực hiện mọi thứ được đề xuất ở trên, với một số tùy chọn để nghiêm ngặt hơn một chút, và tất cả mà không phải thực hiện vô số if (typeof x.method === 'function')
nồi hơi.
Hy vọng ai đó thấy nó hữu ích.
Đây là một câu hỏi cũ, tuy nhiên chủ đề này không bao giờ hết lỗi với tôi.
Vì nhiều câu trả lời ở đây và trên web tập trung vào "thi hành" giao diện, tôi muốn đề xuất một chế độ xem khác:
Tôi cảm thấy thiếu giao diện nhiều nhất khi tôi sử dụng nhiều lớp hoạt động tương tự nhau (tức là thực hiện một giao diện ).
Ví dụ: tôi có Trình tạo email dự kiến sẽ nhận được các phần của Email , "biết" cách tạo nội dung và HTML của các phần. Do đó, tất cả chúng cần phải có một số loại getContent(id)
và getHtml(content)
phương pháp.
Mẫu gần nhất với các giao diện (mặc dù nó vẫn là một cách giải quyết) Tôi có thể nghĩ đến việc sử dụng một lớp sẽ nhận được 2 đối số, sẽ xác định 2 phương thức giao diện.
Thách thức chính với mẫu này là các phương thức hoặc phải static
hoặc lấy đối số của chính nó để truy cập các thuộc tính của nó. Tuy nhiên, có những trường hợp tôi thấy sự đánh đổi này đáng để phiền phức.
class Filterable {
constructor(data, { filter, toString }) {
this.data = data;
this.filter = filter;
this.toString = toString;
// You can also enforce here an Iterable interface, for example,
// which feels much more natural than having an external check
}
}
const evenNumbersList = new Filterable(
[1, 2, 3, 4, 5, 6], {
filter: (lst) => {
const evenElements = lst.data.filter(x => x % 2 === 0);
lst.data = evenElements;
},
toString: lst => `< ${lst.data.toString()} >`,
}
);
console.log('The whole list: ', evenNumbersList.toString(evenNumbersList));
evenNumbersList.filter(evenNumbersList);
console.log('The filtered list: ', evenNumbersList.toString(evenNumbersList));
giao diện trừu tượng như thế này
const MyInterface = {
serialize: () => {throw "must implement serialize for MyInterface types"},
print: () => console.log(this.serialize())
}
tạo một ví dụ:
function MyType() {
this.serialize = () => "serialized "
}
MyType.prototype = MyInterface
và sử dụng nó
let x = new MyType()
x.print()