Przetłumaczenie kawałka kodu z Javy do JavaScript

0

Mam taki kod:

class Base {
    final int var1;
    int var2 = 6;
    
    public Base(final int var1) {
        this.var1 = var1;
    }
    
    int getX() {
        return var1;
    }
}


class Derived extends Base {
    final int var3;
    int var4 = 8;
    
    public Derived(final int var1, final int var3) {
        super(var1);
        this.var3 = var3;
    }

    @Override
    int getX() {
        return super.getX() + var4;
    }
}

Chcę go przetłumaczyć 1:1 do JavaScriptu, bez użycia dodatkowych bibliotek. Jak to zrobić?

Jak na razie umiem napisać w JS tyle:

Base = function(var1) {
  this.var1 = var1;
  this.var2 = 6;
}

Base.prototype.getX = function() {
  return this.var1;
}
1

w zasadzie tak; chcę mieć coś co chociaż przypomina OOP

http://www.typescriptlang.org/Playground/
http://typescript.codeplex.com/

1

Myślenie w JS-ie klasycznie zamiast prototypowo może się okazać Złym Pomysłem. JS jest na tyle elastyczny, że można w nim zaimplementować klasyczne dziedziczenie, ale naturalnie jest prototypowy. Obiekty dziedziczą tu po obiektach. Nie ma dodatkowych bytów zwanych "klasami". W JS-ie jest więc -- obiektywnie rzecz biorąc -- prościej, ze względu na mniejszą liczbę bytów, ale ponieważ języki z dziedziczeniem klasycznym są powszechniejsze, przestawienie się na prototypowość sprawia problemy.

Minusem JS-a jest też to, że udaje nieco Javę tym strasznym słówkiem new. Ten operator jest bardzo nieużyteczny. Dokonuje dziedziczenia prototypowego w bardzo niewygodny sposób. Lepiej już użyć Object.create(proto), które tworzy nowy, pusty obiekt, dziedziczący po proto.

Używając prototypów i nie pisząc żadnych funkcji pomocniczych (które IMO są praktycznie niezbędne jeśli ma to pseudo-klasyczne dziedziczenie jakoś wyglądać), Twój przykład można pociągnąć dalej tak...

var Base = function(var1) {
  this.var1 = var1;
  this.var2 = var2;
};
 
Base.prototype.getX = function() {
  return this.var1;
};

var Derived = function(var1, var3) {
  Base.call(this, var1);
  this.var3 = var3;
  this.var4 = 8;
};
Derived.prototype = new Base(); // niezgrabne: odpalamy konstruktor nie podając mu parametrów...

Derived.prototype.getX = function() {
  return Base.prototype.getX.call(this) + this.var4; // niezgrabne, to powinno być wyabstrahowane do jakiejś funkcji super()/uber()
};

Przyznam, że nie testowałem tego zbyt dokładnie, ale powinno to być mniej więcej coś takiego. Bez funkcji pomocnicznych jest niezbyt zgrabnie, ale spróbuj na tych zasadach zaimplementować w Javie dziedziczenie prototypowe!

Inna obiektowość, bardziej funkcyjna, z prawdziwą hermetyzacją (powyżej, wszystkie składowe są de facto publiczne):

function createBase(var1) {
  var var2 = 6;
  var self = {};

  self.getX = function() {
   return var1;
  };

  return self;
}

function createDerived(var1, var3) {
  var var4 = 8;
  var self = createBase(var1);
  var superGetX = self.getX;

  self.getX = function() {
    return superGetX() + var4;
  };

  return self;
};

Minusem powyższego rozwiązania jest większy koszt tworzenia obiektów niż w przypadku dziedziczenia prototypowego. Ale to rzadko jest problemem, bo w JS-ie rzadko mamy duże grupy obiektów.

W JS-ie generalnie nie tworzy się głębokich hierarchii dziedziczenia. "Nadtypów" jest przeważnie 1, 2. Zwykle nie ma takiej potrzeby, choć trzeba się przestawić.

DRY zachowuje się dzięki funkcyjności. Można prosto tworzyć mixiny.

Pamiętaj, że tu nie ma statycznej kontroli typów. W JS-ie musisz polegać na testach automatycznych. Które i tak wypada pisać, prawda? ;)

0

Okej, no to wygenerowałem coś takiego:

var __extends = this.__extends || function (d, b) {
    function __() {
        this.constructor = d;
    }

    __.prototype = b.prototype;
    d.prototype = new __();
};

Base = (function () {
    var var1;
    var var2 = 6;

    function Base(_var1) {
        var1 = _var1;
    }

    Base.prototype.getX = function () {
        return var1;
    };

    return Base;
})();

Derived = (function (_super) {
    __extends(Derived, _super);

    var var3;
    var var4 = 8;

    function Derived(_var1, _var3) {
        _super.call(this, _var1);
        var3 = _var3;
    }

    Derived.prototype.getX = function () {
        return _super.prototype.getX.call(this) + var4;
    };

    return Derived;
})(Base);

O ile dobrze rozumiem to ten kod teraz odpowiadałby kodowi z pierwszego posta z tym, że pola var1, var2, var3 i var4 są teraz prywatne. Mógłby ktoś na to rzucić okiem i potwierdzić?

Wzorowałem się na przykładzie 'Simple inheritance' z TypeScript Playground + zastosowałem trik z hermetyzacją od bswierczynskiego.

1

@Wibowit:
Niestety, tak nie jest dobrze.

Użyłeś IIFE (Immediately Invoked Function Expresion), które są bardzo przydatnym narzędziem i świetnie nadają się np. do definiowania zmiennych/"stałych" (obecnie nie można korzystać z const w przeglądarkach) statycznych.

IIFE to jednak relatywnie zaawansowana konstrukcja. Wiele osób używa tego na ślepo, ale wszyscy powinni najpierw posiąść odpowiednie informacje o zwykłych wyrażeniach funkcyjnych, no i koniecznie o zakresie ważności (scope) w JavaScripcie.

Tutaj, przez te IIFE, uczyniłeś składowe var1-4 zmiennymi... statycznymi. Tj. jest tylko jedna kopia var1 dla wszystkich obiektów Base. Analogicznie z innymi zmiennymi. Chyba nie o to chodziło, prawda?

Niestety, ciężko pogodzić hermetyzowanie niestatycznych składowych oraz dziedziczenie prototypowe. Praktycznie trzeba olać jedno żeby mieć drugie.

Jeśli chcesz mieć zwykłą, hermetyzowaną składową, musisz ją zadeklarować w konstruktorze. Wtedy będzie istniała jedna kopia tej składowej per jedno wywołanie konstruktora.

A więc musiałbyś zadeklarować te składowe wewnątrz funkcji Base lub Derived, a nie wewnątrz otaczających je IIFE. Wtedy jednak, funkcje getX nie będa miały dostępu do tych zmiennych. Więc i te funkcje będą musiały zostać zadeklarowane w konstruktorze -- podobnie jak ja to zrobiłem. Zysk z dziedziczenia prototypowego robi nam się jednak wtedy co najwyżej niewielki.

0

Niestety masz rację. Skorzystam w takim razie z twojego sposobu.

Jedno mnie trochę odrzuca, mianowicie konieczność tworzenia zmiennej dla każdej nadpisywanej metody jak tutaj:

  var self = createBase(var1);
  var superGetX = self.getX;
 
  self.getX = function() {
    return superGetX() + var4;
  };

Zamieniłem to na coś takiego (wykorzystałem to Object.create() o którym wspomniałeś):

    var _super = newBase(var1);
    var self = Object.create(_super);

    self.getX = function () {
        return _super.getX() + var4;
    };

Wydaje mi się, że to działa dobrze.

Ogólnie jak na razie wymodziłem coś takiego:

function newBase(var1) {
    var var2 = 6;
    var self = {};

    self.getX = function() {
        return var1;
    };

    self.setX = function(_var1) {
        var1 = _var1;
    }

    self.getY = function() {
        return var2;
    };

    self.setY = function(_var2) {
        var2 = _var2;
    };

    return self;
}

function newDerived(var1, var3) {
    var var4 = 8;
    var _super = newBase(var1);
    var self = Object.create(_super);

    self.getX = function () {
        return _super.getX() + var4;
    };

    return self;
}

var first = newBase(5);
first.setY(7);
var second = newBase(6);
second.setY(8);
var derived = newDerived(3, 2);
derived.setX(1);
derived.setY(0);
document.writeln(first.getX());
document.writeln(second.getX());
document.writeln(first.getY());
document.writeln(second.getY());
document.writeln(derived.getX());
document.writeln(derived.getY());

I działa to mniej więcej tak jak powinno.

1 użytkowników online, w tym zalogowanych: 0, gości: 1