読者です 読者をやめる 読者になる 読者になる

console.lealog();

@leader22のWeb系に関する勉強めもブログですのだ

0からはじめるJSX Part.5-2

JavaScript JSX

今回は前回の記事で書いたクラスとインターフェースのところについて、自分用のまとめです。

オブジェクト指向とは何ぞやっていうところのおさらいと、JSXではこう書くっていうのをまとめておきます。

いい加減くどいですが、この記事を書いた人間はオブジェクト指向初挑戦のぺーぺーです。
清々しいほど明らかに勘違いしている部分もあるやもしれませんが、その場合はお手数ですがご指摘いただければなぁと。

前回のサンプルソース

JSX - Tryにて、以下を貼り付けることでそのまま動きます。

interface Flyable {
     abstract function fly() : void;
}

abstract class Animal {
     function eat() : void {
          log "An animal is eating!";
     }
}

class Bat extends Animal implements Flyable {
     override function fly() : void {
          log "A bat is flying!";
     }
}

abstract class Insect {
}

class Bee extends Insect implements Flyable {
     override function fly() : void {
          log "A bee is flying!";
     }
}

class Test {

     static function run() : void {
          // fo bar
          var bat = new Bat();
         
          var animal : Animal = bat; // OK. A bat is an animal.
          animal.eat();
         
          var flyable : Flyable = bat; // OK. A bat can fly
          flyable.fly();
         
          // for Bee
          var bee = new Bee();
         
          flyable = bee; // A bee is also flyable
          flyable.fly();
     }

}

さて、クラスって何やねん!からスタートした初学者にとっては、イヤな感じしかしないですね。

  • extendsとimplementsって何が違うんかな!
  • overrideってのもあるやん!なにこれ!
  • interfaceってなんですか、型はきっちり決めないといけないんじゃなかったんですか。
  • それにちゃっかり書いてるけど、abstractってなんやねんな。
  • finalとかnativeとか何に何をつけたらええの?

とまぁ、あの頃は色んな疑問が爆発したわけですよ。
一つずつ潰していきます。

クラスのおさらい

Javaの入門書とか見てもさっぱり。
研修の講師があれこれ言っててもさっぱり。
そんな私が、最近やっとクラスベースの考え方っていうのを理解できてきた感じがあるので、それを文章に。

プログラミングする以上、何か目的があるからそうするわけで、何かメリットがあるからその書き方を選ぶわけで。
その目的を最初からズバッと教えてくれれば、つべこべ言わずあれこれ考えず、言われるがままに覚えたのに。
それを最初から言わないからあれこれ考えてドツボにはまり、つべこべ言って講師が逆ギレするんやで。

さて、クラスベースとか、オブジェクト指向とか、そういった概念を理解するには、ゲームを開発するシーンを想像するのが一番わかりやすいなぁと、最近思います。

最大の目的は効率化:部品化

なんでこんなまどろっこしい書き方するんやろなぁーって疑問に思ったあの頃。
でもそうした方がスマートやし、後から直すのも楽。
変数を使うこと自体は抵抗なく覚えたのに、なんでクラスはあかんかったんかは永遠の謎。

とあるRPGゲームを作るとして、フィールドに配置するキャラクターを色々用意する必要があるとします。

  • キャラクターはフィールドに配置されます。
  • キャラクターにはプレーヤー、モンスター、NPCの種類があります。
  • プレーヤーは操作することで動き、モンスターは勝手に動きますが、NPCは動きません。
  • プレーヤーとNPCだけが会話することができます。

みたいな設定の時に、オブジェクト指向がその真価を発揮します。

// JSX風ではあるものの、便宜上の書き方をしてます・・。

// 会話できる属性
interface Conversation{
     abstract function saySomething() : void;
}

// 動ける属性
interface Moving{
     abstract function moveAnywhere() : void;
}

// 全ての基本となるクラス
abstract class Character{
     function showOnField(): void {
      // フィールドに配置される・・
     }
}

// モンスターは動ける属性だけ
final class Monster extends Character implements Moving{
     override function moveAnywhere(): void{
          // Monsterは2倍速く動く!とか。
     }
}

// プレーヤーは動けるし話せる
final class Player extends Character implements Moving, Conversation{
     override function saySomething(): void{
          log 'プレーヤーです!';
     }

     override function moveAnywhere(): void{
          // Playerはアイテム使ったときだけ早く動くけど普段は普通とか。
     }
}

// NPCは話せるだけ
final class Npc extends Character implements Conversation{
     override function saySomething(): void{
          log 'NPCです!';
     }
}

みたいな。

こうしておけば

プレーヤー、モンスター、NPCの3つをそれぞれプログラムするよりも良いですよね。

  • すっきり書けるし。
  • シャイなゲームにしたければ、最初のConversationの1箇所だけ塞げば良いし。
  • 喋れるボスキャラをプログラムする時も、似たようなやり方で実装できちゃう。

という感じに便利なのがクラスベースの考え方というところで自己完結して、本題。

修飾子いっぱい

サンプルソースを漁ると、あれやこれや出てきます。
ドキュメントないので何が本当か探すのも大変です。

expected keyword: class interface mixin abstract final native __fake__
expected keyword: function var static abstract override final const native __readonly__

クラスや変数を宣言しようとした時に、変な宣言すると出るエラーです。
というわけで、察するに何かを宣言する時に使えるパターンは、これらの組み合わせってことがわかります。

それぞれが何かを見ていくと・・。

interface

interface Flyable {
     abstract function fly() : void;
}

class Bee implements Flyable {
     override function fly() : void {
          log "A bee is flying!";
     }
}

このクラスベースの言語では、基礎となるクラスを用意して、それに+αで必要な機能を拡張したりしていきます。
で、そこで注意しないといけないのが、無闇矢鱈と拡張できるわけではないということ。

上の例でいうInsectクラスを元にBeeクラスを作ることを、いわゆる継承って言いますが、JSXでは一つのクラスしか継承できません。
あっちのクラスもこっちのクラスも継承して、新しいの作りたい!って思ってもできません。
多重継承を認めないこと自体は、他の言語でもあるみたいです。

そこで登場するのがこのinterfaceだそうな。
これは、

  • abstractな関数のみを有します。(abstractについては後述)
  • 普通にvarとかはできる。
  • implements interfaceName のように、extendsではなくimplementsされるのがinterfaceです。
  • interfaceがinterfaceを実装することもできる。
  • interfaceを実装したクラスでは、必ずinterfaceが持つabstractなメンバを実装する必要がある。
  • implementsの場合は、他のinterfaceと一緒にいくつでも実装できます。

extendsすることを継承、implemetnsすることを実装って言うらしい。

「〜属性」みたいな機能をつけたい場合は、interfaceでやりましょうってイメージでしょうか。
そのくせサンプルのソースにほっとんど出てこないのはなんで?いらない子?

これはもうちょっと調べる必要があるかなぁ。

mixin

これだけはJavaのリファレンスにも載ってなかったんですよね。
そんで調べてもよーわからんし。
ソースを読みあさった上での今の結論は、

  • interfaceとclassの間みたいな性質。
  • interfaceと違って、abstractじゃない関数も有することができる。
  • interfaceと同じでimplementsする。
  • interfaceと同じで何度でもimplementsできる。

abstract

いわゆる抽象〜シリーズ。
クラス宣言時に使っても良いし、関数や変数に使っても良い。

抽象メソッド

関数の中身は実装せず、宣言だけ。
interfaceの中によく登場しますね。

interface Flyable {
     abstract function fly() : void; // コレのこと
}
抽象クラス

継承されるまでインスタンス化できません。
ということで、継承されることを前提としているクラスには、abstractがつく模様。

abstract class Animal { // コレのこと
     function eat() : void {
          log "An animal is eating!";
     }
}

抽象=後でちゃんと実装するから!=不完全なやつみたいなイメージで良いんかな。

というか、staticかnewしないと基本的にクラスは何もできないって感じ?

override

オーバーライド!
いわゆる継承したクラスのメソッドを、改めて実装するやつですね。

abstract class Animal {
     function eat() : void {
          log "An animal is eating!";
     }
}

final class Human extends Animal{
    override function eat(): void{ // コレ!
      log "Human also like eating!";
    }
}
class Test {
  static function run() : void {
    var h = new Human();
    h.eat(); // Human also like eating!
  }
}

オーバーライドする時は、必ずoverrideってつけないといけないそうです。

final

抽象とは対照的に、これで完結!みたいな。
最終的な成果物?あっちこっち継承して実装して出来上がり!っていうものにつけられる模様。

interface Phonable{
  // ...
}
abstract class iPodTouch{
  // ...
}

final class iPhone extends iPodTouch implements Phonable{
  // ...
}

final functionとかだと、後でoverrideできないとか、変数でも代入できないとか。

static

基本的にはnewして使うクラスベースの言語において、newしなくても使えるようにするための修飾子。
内部的に使う変数とかじゃなくて、何度も参照されるような設定値系のクラスに使われてるのをよく見かけたので、そういう使い方なんでしょうね。
後は実行する本体のクラスとか。

class Config {
	static var quantity = 360;
	static const size     = 2.0;
	static const decay    = 0.98;
	static const gravity  = 2.0;
	static const speed    = 6.0;
}

final class _Main {
	static function main() : void {
             // Config.quantity...
	}
}

static class..で始まるやつは見かけないので、そういう使い方はしないんでしょう。

const

finalとの違いがわかりませんが、おそらく変数にはこれを使うっぽい。

class Config {
	static var quantity = 360;
	static const size     = 2.0;
	static const decay    = 0.98;
	static const gravity  = 2.0;
	static const speed    = 6.0;
}

static const hogehogeって使い方が基本らしい。

nativeと__fake__と__readonly__

一応載せたけど本当に使えるのかどうかもわからず・・。
調べてみたけど正体もわからず・・。

サンプルで見かけたのがあったので載せておきます。

native __fake__ class Foo {
  var name : string;
}

class Test {
  static function run() : void {
    var f : Foo = { name: "hello" } as __noconvert__ Foo;
    log f.name;
  }
}
  • まったく意味がわかりません。
  • __readonly__にいたっては影も形もない!
  • __noconvert__ってなんですか・・。

どなたか解説を・・。

nativeに至っては他の言語で実装する時に・・・とか書いてありまして、もう意味不明です。
後で紹介しますが、ブラウザAPIを使えるようにインポートするモジュールの中にいっぱい書いてあったです。

まとめ

まとめてみたものの、たぶんベストプラクティス的にパターン化されてるものがあって、それを覚えてそれを使うのが一番良いんやろなぁ・・。
ドキュメント読むなんてプログラム勉強する第一歩にすらなってない感があるやね!
そして用語に翻弄されてるっていう。

どういう区分でabstractにするとかinterfaceにするとか、設計面での想像がつかないのがなぁ。

やっぱコードリーディングが大事ということで。
チュートリアル終わったらサンプルソースを見ていこう。