JavaScript에서 추상 기본 클래스를 어떻게 만듭니 까?


109

JavaScript에서 추상 기본 클래스를 시뮬레이션 할 수 있습니까? 가장 우아한 방법은 무엇입니까?

다음과 같이하고 싶습니다.-

var cat = new Animal('cat');
var dog = new Animal('dog');

cat.say();
dog.say();

출력되어야합니다 : 'bark', 'meow'


답변:


127

추상 클래스를 만드는 간단한 방법은 다음과 같습니다.

/**
 @constructor
 @abstract
 */
var Animal = function() {
    if (this.constructor === Animal) {
      throw new Error("Can't instantiate abstract class!");
    }
    // Animal initialization...
};

/**
 @abstract
 */
Animal.prototype.say = function() {
    throw new Error("Abstract method!");
}

Animal"클래스"및 say방법은 추상적이다.

인스턴스를 만들면 오류가 발생합니다.

new Animal(); // throws

이것이 "상속"하는 방법입니다.

var Cat = function() {
    Animal.apply(this, arguments);
    // Cat initialization...
};
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.say = function() {
    console.log('meow');
}

Dog 똑같이 보입니다.

그리고 이것이 당신의 시나리오가 진행되는 방식입니다.

var cat = new Cat();
var dog = new Dog();

cat.say();
dog.say();

여기 에서 바이올린을 켜 십시오 (콘솔 출력을보십시오).


내가 OOP를 처음 접했기 때문에 마음에 들지 않으면 한 줄씩 설명해 주시겠습니까? 감사!
React Developer

2
@undefined : 이해하려면 Javascript에서 프로토 타입 상속을 찾아 보는 것이 좋습니다. 이것은 좋은 가이드입니다.
Jordão

답장을 보내 주셔서 감사합니다 .. 링크를 통해 이동합니다.
React Developer

가장 중요한 부분은 첫 번째 코드 스 니펫에서 오류가 발생한다는 것입니다. 응용 프로그램 실행을 계속하기 위해 경고를 발생 시키거나 객체 대신 null 값을 반환 할 수도 있습니다. 실제로 구현에 따라 다릅니다. 이것은 추상적 인 JS "클래스"를 구현하는 올바른 방법이라고 생각합니다.
dudewad

1
@ G1P : Javascript에서 "수퍼 클래스 생성자"를 실행하는 일반적인 방법이며 수동으로 수행해야합니다.
Jordão

46

JavaScript 클래스 및 상속 (ES6)

ES6에 따르면 JavaScript 클래스와 상속을 사용하여 필요한 작업을 수행 할 수 있습니다.

ECMAScript 2015에 도입 된 JavaScript 클래스는 주로 JavaScript의 기존 프로토 타입 기반 상속에 대한 구문 적 설탕입니다.

참조 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

우선 추상 클래스를 정의합니다. 이 클래스는 인스턴스화 할 수 없지만 확장 할 수 있습니다. 또한이를 확장하는 모든 클래스에서 구현되어야하는 함수를 정의 할 수 있습니다.

/**
 * Abstract Class Animal.
 *
 * @class Animal
 */
class Animal {

  constructor() {
    if (this.constructor == Animal) {
      throw new Error("Abstract classes can't be instantiated.");
    }
  }

  say() {
    throw new Error("Method 'say()' must be implemented.");
  }

  eat() {
    console.log("eating");
  }
}

그 후에 구체적인 클래스를 만들 수 있습니다. 이러한 클래스는 추상 클래스의 모든 기능과 동작을 상속합니다.

/**
 * Dog.
 *
 * @class Dog
 * @extends {Animal}
 */
class Dog extends Animal {
  say() {
    console.log("bark");
  }
}

/**
 * Cat.
 *
 * @class Cat
 * @extends {Animal}
 */
class Cat extends Animal {
  say() {
    console.log("meow");
  }
}

/**
 * Horse.
 *
 * @class Horse
 * @extends {Animal}
 */
class Horse extends Animal {}

그리고 그 결과 ...

// RESULTS

new Dog().eat(); // eating
new Cat().eat(); // eating
new Horse().eat(); // eating

new Dog().say(); // bark
new Cat().say(); // meow
new Horse().say(); // Error: Method say() must be implemented.

new Animal(); // Error: Abstract classes can't be instantiated.

27

다음과 같은 것을 의미합니까?

function Animal() {
  //Initialization for all Animals
}

//Function and properties shared by all instances of Animal
Animal.prototype.init=function(name){
  this.name=name;
}
Animal.prototype.say=function(){
    alert(this.name + " who is a " + this.type + " says " + this.whattosay);
}
Animal.prototype.type="unknown";

function Cat(name) {
    this.init(name);

    //Make a cat somewhat unique
    var s="";
    for (var i=Math.ceil(Math.random()*7); i>=0; --i) s+="e";
    this.whattosay="Me" + s +"ow";
}
//Function and properties shared by all instances of Cat    
Cat.prototype=new Animal();
Cat.prototype.type="cat";
Cat.prototype.whattosay="meow";


function Dog() {
    //Call init with same arguments as Dog was called with
    this.init.apply(this,arguments);
}

Dog.prototype=new Animal();
Dog.prototype.type="Dog";
Dog.prototype.whattosay="bark";
//Override say.
Dog.prototype.say = function() {
        this.openMouth();
        //Call the original with the exact same arguments
        Animal.prototype.say.apply(this,arguments);
        //or with other arguments
        //Animal.prototype.say.call(this,"some","other","arguments");
        this.closeMouth();
}

Dog.prototype.openMouth=function() {
   //Code
}
Dog.prototype.closeMouth=function() {
   //Code
}

var dog = new Dog("Fido");
var cat1 = new Cat("Dash");
var cat2 = new Cat("Dot");


dog.say(); // Fido the Dog says bark
cat1.say(); //Dash the Cat says M[e]+ow
cat2.say(); //Dot the Cat says M[e]+ow


alert(cat instanceof Cat) // True
alert(cat instanceof Dog) // False
alert(cat instanceof Animal) // True

아마도 내가 놓친 것 같습니다. 기본 클래스 (동물) 추상은 어디에 있습니까?
HairOfTheDog 2014 년

5
@HairOfTheDog 예, 당신은 이것이 약 5 년 전에 답변되었고, 당시 자바 스크립트에는 추상적 인 클래스가 없었고, 그 질문은 그것을 시뮬레이션 하는 방법에 대한 것이었고 ( 동물에whattosay 정의되어 있지 않음 )이 답변이 명확하게 묻는다는 것을 놓쳤습니다. 제안 된 답변이 질문자가 찾고있는 것이라면. javascript의 추상 클래스에 대한 솔루션을 제공한다고 주장하지 않습니다. 질문자는 저나 다른 사람에게 답장하지 않았기 때문에 그것이 그에게 효과가 있는지 모르겠습니다. 다른 사람의 질문에 대해 5 년 된 제안 된 답변이 당신에게 맞지 않았다면 죄송합니다.
일부


11

JavaScript에서 추상 기본 클래스를 시뮬레이션 할 수 있습니까?

확실히. JavaScript에서 클래스 / 인스턴스 시스템을 구현하는 방법은 약 천 가지가 있습니다. 다음은 하나입니다.

// Classes magic. Define a new class with var C= Object.subclass(isabstract),
// add class members to C.prototype,
// provide optional C.prototype._init() method to initialise from constructor args,
// call base class methods using Base.prototype.call(this, ...).
//
Function.prototype.subclass= function(isabstract) {
    if (isabstract) {
        var c= new Function(
            'if (arguments[0]!==Function.prototype.subclass.FLAG) throw(\'Abstract class may not be constructed\'); '
        );
    } else {
        var c= new Function(
            'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
            'if (arguments[0]!==Function.prototype.subclass.FLAG && this._init) this._init.apply(this, arguments); '
        );
    }
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass.FLAG);
    return c;
}
Function.prototype.subclass.FLAG= new Object();

var cat = new Animal ( 'cat');

물론 이것은 추상적 인 기본 클래스가 아닙니다. 다음과 같은 의미입니까?

var Animal= Object.subclass(true); // is abstract
Animal.prototype.say= function() {
    window.alert(this._noise);
};

// concrete classes
var Cat= Animal.subclass();
Cat.prototype._noise= 'meow';
var Dog= Animal.subclass();
Dog.prototype._noise= 'bark';

// usage
var mycat= new Cat();
mycat.say(); // meow!
var mygiraffe= new Animal(); // error!

왜 사악한 new Function (...) 구조를 사용합니까? var c = function () {...}; 나아지 다?
fionbio

2
"var c = function () {...}"는 subclass () 또는 기타 포함 범위에있는 모든 것에 대해 클로저를 생성합니다. 아마도 중요하지는 않지만 잠재적으로 원하지 않는 상위 범위를 정리하고 싶었습니다. 순수 텍스트 Function () 생성자는 클로저를 피합니다.
bobince

10
Animal = function () { throw "abstract class!" }
Animal.prototype.name = "This animal";
Animal.prototype.sound = "...";
Animal.prototype.say = function() {
    console.log( this.name + " says: " + this.sound );
}

Cat = function () {
    this.name = "Cat";
    this.sound = "meow";
}

Dog = function() {
    this.name = "Dog";
    this.sound  = "woof";
}

Cat.prototype = Object.create(Animal.prototype);
Dog.prototype = Object.create(Animal.prototype);

new Cat().say();    //Cat says: meow
new Dog().say();    //Dog says: woof 
new Animal().say(); //Uncaught abstract class! 

서브 클래스의 생성자가 슈퍼 클래스의 생성자를 호출 할 수 있습니까? (그래서, 그때는 무조건 예외 올릴 경우 ... 어떤 언어로 슈퍼를 호출 같은
nonopolarity

5
function Animal(type) {
    if (type == "cat") {
        this.__proto__ = Cat.prototype;
    } else if (type == "dog") {
        this.__proto__ = Dog.prototype;
    } else if (type == "fish") {
        this.__proto__ = Fish.prototype;
    }
}
Animal.prototype.say = function() {
    alert("This animal can't speak!");
}

function Cat() {
    // init cat
}
Cat.prototype = new Animal();
Cat.prototype.say = function() {
    alert("Meow!");
}

function Dog() {
    // init dog
}
Dog.prototype = new Animal();
Dog.prototype.say = function() {
    alert("Bark!");
}

function Fish() {
    // init fish
}
Fish.prototype = new Animal();

var newAnimal = new Animal("dog");
newAnimal.say();

이것은 __proto__표준 변수가 아니므 로 작동이 보장 되지는 않지만 적어도 Firefox 및 Safari에서 작동합니다.

작동 방식을 이해하지 못한다면 프로토 타입 체인에 대해 읽어보십시오.


proto 는 FF와 Chome에서만 AFAIK를 작동합니다 (IE도 Opera도 지원하지 않습니다. Safari에서는 테스트하지 않았습니다). BTW, 당신은 잘못하고 있습니다 : 새로운 유형의 동물을 원할 때마다 기본 클래스 (동물)를 편집해야합니다.
일부

Safari와 Chrome은 모두 동일한 자바 스크립트 엔진을 사용합니다. 나는 그가 상속이 어떻게 작동하는지 알고 싶을 뿐이라는 확신이 없었기 때문에 가능한 한 그의 모범을 따르려고 노력했습니다.
Georg Schölly

4
Safari와 Chrome은 동일한 JavaScript 엔진을 사용하지 않고 Safari는 JavaScriptCore를 사용 하며 Chrome은 V8을 사용합니다 . 두 브라우저가 공유하는 것은 레이아웃 엔진 인 WebKit 입니다.
CMS

@ GeorgSchölly 새로운 Object.getPrototypeOf 구조 사용할 답변을 수정하십시오
벤자민 Gruenbaum

@ Benjamin : Mozilla에 따르면setPrototypeOf 아직 내 코드에 필요한 방법 이 없습니다 .
Georg Schölly 2013 년

5

객체 프로토 타입을 사용하여 추상 클래스를 만들 수 있습니다. 간단한 예제는 다음과 같습니다.

var SampleInterface = {
   addItem : function(item){}  
}

위의 방법을 변경할 수 있는지 여부는 구현할 때 귀하에게 달려 있습니다. 자세한 관찰은 여기 를 방문 하십시오 .


5

질문은 꽤 오래되었지만 추상 "클래스"를 만들고 해당 유형의 객체 생성을 차단하는 방법에 대한 몇 가지 가능한 솔루션을 만들었습니다.

//our Abstract class
var Animal=function(){
  
    this.name="Animal";
    this.fullname=this.name;
    
    //check if we have abstract paramater in prototype
    if (Object.getPrototypeOf(this).hasOwnProperty("abstract")){
    
    throw new Error("Can't instantiate abstract class!");
    
    
    }
    

};

//very important - Animal prototype has property abstract
Animal.prototype.abstract=true;

Animal.prototype.hello=function(){

   console.log("Hello from "+this.name);
};

Animal.prototype.fullHello=function(){

   console.log("Hello from "+this.fullname);
};

//first inheritans
var Cat=function(){

	  Animal.call(this);//run constructor of animal
    
    this.name="Cat";
    
    this.fullname=this.fullname+" - "+this.name;

};

Cat.prototype=Object.create(Animal.prototype);

//second inheritans
var Tiger=function(){

    Cat.call(this);//run constructor of animal
    
    this.name="Tiger";
    
    this.fullname=this.fullname+" - "+this.name;
    
};

Tiger.prototype=Object.create(Cat.prototype);

//cat can be used
console.log("WE CREATE CAT:");
var cat=new Cat();
cat.hello();
cat.fullHello();

//tiger can be used

console.log("WE CREATE TIGER:");
var tiger=new Tiger();
tiger.hello();
tiger.fullHello();


console.log("WE CREATE ANIMAL ( IT IS ABSTRACT ):");
//animal is abstract, cannot be used - see error in console
var animal=new Animal();
animal=animal.fullHello();

마지막 객체가 오류를주는 것을 볼 수 있듯이 프로토 타입의 Animal이 property를 가지고 있기 때문 abstract입니다. Animal.prototype프로토 타입 체인에 있는 것이 아닌 동물인지 확인하기 위해 다음을 수행합니다.

Object.getPrototypeOf(this).hasOwnProperty("abstract")

그래서 가장 가까운 프로토 타입 객체에 abstract속성 이 있는지 확인합니다. 프로토 타입 에서 직접 생성 된 객체 만이 Animal조건을 true로 설정합니다. 함수 hasOwnProperty는 프로토 타입이 아닌 현재 객체의 속성 만 확인하므로 속성이 프로토 타입 체인이 아닌 여기에서 선언되었는지 100 % 확신 할 수 있습니다.

Object의 자손 인 모든 객체는 hasOwnProperty 메서드를 상속합니다 . 이 메서드는 개체에 해당 개체의 직접 속성으로 지정된 속성이 있는지 여부를 확인하는 데 사용할 수 있습니다. in 연산자와 달리이 메서드는 개체의 프로토 타입 체인을 확인하지 않습니다. 그것에 대해 더 알아보기 :

내 제안에서 우리 는 @ Jordão의 현재 베스트 답변처럼 constructor매번 변경할 필요가 없습니다 Object.create.

솔루션은 또한 계층 구조에서 많은 추상 클래스를 생성 할 수있게하므로 abstract프로토 타입에서 속성 을 생성 하기 만하면됩니다.


4

강제 할 수있는 또 다른 것은 추상 클래스가 인스턴스화되지 않았는지 확인하는 것입니다. Abstract 클래스 생성자로 설정된 FLAG 함수로 작동하는 함수를 정의하여이를 수행 할 수 있습니다. 그러면 throw 될 예외를 포함하는 생성자를 호출하는 FLAG를 생성하려고합니다. 아래 예 :

(function(){

    var FLAG_ABSTRACT = function(__class){

        throw "Error: Trying to instantiate an abstract class:"+__class
    }

    var Class = function (){

        Class.prototype.constructor = new FLAG_ABSTRACT("Class");       
    }

    //will throw exception
    var  foo = new Class();

})()


2

Factory이 경우 디자인 패턴을 사용할 수 있습니다 . Javascript를 사용 prototype하여 부모의 구성원을 상속합니다.

부모 클래스 생성자를 정의합니다.

var Animal = function() {
  this.type = 'animal';
  return this;
}
Animal.prototype.tired = function() {
  console.log('sleeping: zzzZZZ ~');
}

그런 다음 어린이 클래스를 만듭니다.

// These are the child classes
Animal.cat = function() {
  this.type = 'cat';
  this.says = function() {
    console.log('says: meow');
  }
}

그런 다음 자식 클래스 생성자를 정의합니다.

// Define the child class constructor -- Factory Design Pattern.
Animal.born = function(type) {
  // Inherit all members and methods from parent class,
  // and also keep its own members.
  Animal[type].prototype = new Animal();
  // Square bracket notation can deal with variable object.
  creature = new Animal[type]();
  return creature;
}

그것을 테스트하십시오.

var timmy = Animal.born('cat');
console.log(timmy.type) // cat
timmy.says(); // meow
timmy.tired(); // zzzZZZ~

전체 예제 코딩에 대한 Codepen 링크 는 다음과 같습니다 .


1
//Your Abstract class Animal
function Animal(type) {
    this.say = type.say;
}

function catClass() {
    this.say = function () {
        console.log("I am a cat!")
    }
}
function dogClass() {
    this.say = function () {
        console.log("I am a dog!")
    }
}
var cat = new Animal(new catClass());
var dog = new Animal(new dogClass());

cat.say(); //I am a cat!
dog.say(); //I am a dog!

이것은 JavaScript의 다형성입니다. 재정의를 구현하기 위해 몇 가지 검사를 수행 할 수 있습니다.
Paul Orazulike 2017

0

나는 All That 답변 특히 처음 두 ( some and jordão )가 기존 프로토 타입 기본 JS 개념으로 질문에 명확하게 대답 한다고 생각 합니다.
이제 동물 클래스 생성자가 구성에 전달 된 매개 변수에 따라 동작하기를 원하므로 이것은 Creational Patterns예를 들어 Factory Pattern 의 기본 동작과 매우 유사하다고 생각합니다 .

여기에서는 그렇게 작동하도록 약간의 접근 방식을 만들었습니다.

var Animal = function(type) {
    this.type=type;
    if(type=='dog')
    {
        return new Dog();
    }
    else if(type=="cat")
    {
        return new Cat();
    }
};



Animal.prototype.whoAreYou=function()
{
    console.log("I am a "+this.type);
}

Animal.prototype.say = function(){
    console.log("Not implemented");
};




var Cat =function () {
    Animal.call(this);
    this.type="cat";
};

Cat.prototype=Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.say=function()
{
    console.log("meow");
}



var Dog =function () {
    Animal.call(this);
    this.type="dog";
};

Dog.prototype=Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.say=function()
{
    console.log("bark");
}


var animal=new Animal();


var dog = new Animal('dog');
var cat=new Animal('cat');

animal.whoAreYou(); //I am a undefined
animal.say(); //Not implemented


dog.whoAreYou(); //I am a dog
dog.say(); //bark

cat.whoAreYou(); //I am a cat
cat.say(); //meow

이것에 연결 : programmers.stackexchange.com/questions/219543/…Animal생성자는 안티 패턴으로 볼 수 있습니다. 수퍼 클래스는 서브 클래스에 대한 지식이 없어야합니다. (Liskov 및 Open / Close 원칙 위반)
SirLenz0rlot

0
/****************************************/
/* version 1                            */
/****************************************/

var Animal = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};
var Cat = function() {
    Animal.call(this, "moes");
};

var Dog = function() {
    Animal.call(this, "vewa");
};


var cat = new Cat();
var dog = new Dog();

cat.say();
dog.say();


/****************************************/
/* version 2                            */
/****************************************/

var Cat = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};

var Dog = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};

var Animal = function(type) {
    var obj;

    var factory = function()
    {
        switch(type)
        {
            case "cat":
                obj = new Cat("bark");
                break;
            case "dog":
                obj = new Dog("meow");
                break;
        }
    }

    var init = function()
    {
        factory();
        return obj;
    }

    return init();
};


var cat = new Animal('cat');
var dog = new Animal('dog');

cat.say();
dog.say();

내 관점에서 이것은 적은 코드로 좋은 결과를 얻을 수있는 가장 우아한 방법입니다.
Tamas Romeo

1
이것이 유용한 이유를 설명하십시오. 코드를 배포하는 것은 코드가 유용한 이유를 설명하는 것만 큼 유용하지 않습니다. 누군가에게 물고기를 건네주는 것과 낚시하는 법을 가르치는 것의 차이입니다.
The Tin Man

많은 사람들이 자바 스크립트 프로그래밍 기술 프로토 타입이나 생성자에서 사용하지 않습니다. 많은 상황에서 유용하더라도. 이들에게는 코드가 유용하다고 생각합니다. 아니 코드 때문에 다른 사람보다 낫다 ..하지만 이해하기 쉽기 때문에
타마스 로미오

0

기본 클래스와 해당 멤버가 엄격하게 추상적인지 확인하려면 다음을 수행하는 기본 클래스가 있습니다.

class AbstractBase{
    constructor(){}
    checkConstructor(c){
        if(this.constructor!=c) return;
        throw new Error(`Abstract class ${this.constructor.name} cannot be instantiated`);
    }
    throwAbstract(){
        throw new Error(`${this.constructor.name} must implement abstract member`);}    
}

class FooBase extends AbstractBase{
    constructor(){
        super();
        this.checkConstructor(FooBase)}
    doStuff(){this.throwAbstract();}
    doOtherStuff(){this.throwAbstract();}
}

class FooBar extends FooBase{
    constructor(){
        super();}
    doOtherStuff(){/*some code here*/;}
}

var fooBase = new FooBase(); //<- Error: Abstract class FooBase cannot be instantiated
var fooBar = new FooBar(); //<- OK
fooBar.doStuff(); //<- Error: FooBar must implement abstract member
fooBar.doOtherStuff(); //<- OK

Strict 모드에서는 throwAbstract 메서드에서 호출자를 기록 할 수 없지만 스택 추적을 표시하는 디버그 환경에서 오류가 발생해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.