
この記事は、Dartスタイルのコードの書き方を学ぶのに役立ちます。 言語はまだ活発に開発されているため、多くのイディオムも将来変更される可能性があります。 いくつかの場所では、ベストプラクティスをまだ決定していません(おそらくあなたは私たちを助けることができますか?)それにもかかわらず、ここでは、JavaまたはJavaScriptからDartモードに頭を切り替えるために注意すべきいくつかのポイントがあります。
コンストラクター
この記事は、オブジェクトが人生から始まるのと同じ方法で-デザイナーから始めます。 各オブジェクトはコンストラクターで作成する必要があり、その定義は品質クラスを作成する重要なポイントです。 Dartには興味深い機能がいくつかあります。
自動フィールド初期化
開始するには、退屈な繰り返しを取り除きます。 多くの場合、コンストラクタは引数を取り、その値をクラスフィールドに単純に割り当てます。
class Point { num x, y; Point(num x, num y) { this.x = x; this.y = y; } }
フィールドを初期化するためだけに
x
4回入力する必要がありました。 それは吸う! これを行う方が良いです:
class Point { num x, y; Point(this.x, this.y); }
コンストラクター引数のリストにある場合、
this.
は引数nameが先行します
this.
、この名前のフィールドは引数値で自動的に初期化されます。 この例では、別の小さなトリックが使用されました-コンストラクター本体が空の場合、それを使用できます
;
{}
代わりに。
名前付きコンストラクター
ほとんどの動的言語と同様に、Dartはオーバーロードをサポートしていません。 メソッドの場合、別の名前を思い付くことができるので、これはそれほど怖いことではありません。 デザイナーはそれほど幸運ではなかった。 それらの運命を促進するために、Dartでは名前付きコンストラクターの使用を許可しています。
class Point { num x, y; Point(this.x, this.y); Point.zero() : x = 0, y = 0; Point.polar(num theta, num radius) { x = Math.cos(theta) * radius; y = Math.sin(theta) * radius; } }
Point
クラスには、レギュラーと2つの名前付きの3つのコンストラクターがあります。 使用方法は次のとおりです。
var a = new Point(1, 2); var b = new Point.zero(); var c = new Point.polar(Math.PI, 4.0);
名前付きコンストラクタで
new
を使用することに注意してください;これらは通常の静的メソッドではありません。
工場のコンストラクター
工場設計パターンを使用すると便利な場合があります。 たとえば、クラスのインスタンスを作成する必要がありますが、ある程度の柔軟性が必要です。特定のタイプのコンストラクター呼び出しをハードコードするだけでは十分ではありません。 おそらく、キャッシュされたインスタンス(存在する場合)または異なるタイプのオブジェクトを返したいでしょう。
Dartでは、オブジェクトが作成された場所のコードを変更せずにこれを行うことができます。 通常のコンストラクターと同様に呼び出されるファクトリーコンストラクターを作成できます。 例:
class Symbol { final String name; static Map<String, Symbol> _cache; factory Symbol(String name) { if (_cache == null) { _cache = {}; } if (_cache.containsKey(name)) { return _cache[name]; } else { final symbol = new Symbol._internal(name); _cache[name] = symbol; return symbol; } } Symbol._internal(this.name); }
Symbol
クラスを定義しました。 文字は文字列とほぼ同じですが、指定された名前の文字が常に1つだけであることを保証する必要があります。 これにより、同じオブジェクトを指していることを確認するだけで、文字が等しいかどうかを安全に確認できます。
デフォルトのコンストラクター(匿名)を定義する前に、
factory
キーワードが見つけられます。 呼び出されたとき、新しいオブジェクトは作成されません(
this
ファクトリコンストラクタにありません)。 代わりに、オブジェクトを明示的に作成して返す必要があります。 この例では、最初にキャッシュに同じ名前の文字があるかどうかを確認し、ある場合はそれを返します。
これらすべてが呼び出しコードに対して透過的であることはクールです。
var a = new Symbol('something'); var b = new Symbol('something');
2番目の呼び出しは、実際には新しいキャラクターを作成するのではなく、既存のキャラクターを返します。 これは便利です。最初にファクトリーコンストラクターが必要でなく、その後にコンストラクターがあることが判明した場合、静的ファクトリーメソッドを呼び出すためにコードのどこかで
new
を変更する必要がないためです。
機能
最近のほとんどの言語と同様に、Dart関数はクロージャーとより軽い構文を備えたファーストクラスのオブジェクトです。 関数はオブジェクトであり、ためらうことなく何でもできます。 イベントハンドラーの関数を広範囲に使用します。
Dartで関数を作成するには3つの方法があります。 最初の名前は関数です:
void sayGreeting(String salutation, String name) { final greeting = '$salutation $name'; print(greeting); }
Cの通常の関数宣言、またはJavaまたはJavaScriptのメソッドのように見えます。 CやC ++とは異なり、関数宣言はネストできます。 2番目の方法は、匿名関数です。
window.on.click.add((event) { print('You clicked the window.'); })
この例では、匿名関数をイベントハンドラーとして
add()
メソッドに渡します。 最後に、単一の式で構成される小さな関数には、軽量の構文があります。
var items = [1, 2, 3, 4, 5]; var odd = items.filter((i) => i % 2 == 1); print(odd); // [1, 3, 5]
かっこ内の引数とそれに続く矢印(
=>
)および式は、この引数を取り、式の評価結果を返す関数を作成します。
実際には、(表現力を損なうのではなく)簡潔であるため、可能な限り矢印表記を使用することを好みます。 多くの場合、イベントハンドラーとコールバックに匿名関数を使用します。 名前付き関数はほとんど使用されません。
Dartにはもう1つのトリックがあります(これは私のお気に入りの言語機能の1つです)-
=>
を使用してクラスメンバーを定義できます。 もちろん、次のようにできます:
class Rectangle { num width, height; bool contains(num x, num y) { return (x < width) && (y < height); } num area() { return width * height; } }
しかし、可能であれば、なぜこのように:
class Rectangle { num width, height; bool contains(num x, num y) => (x < width) && (y < height); num area() => width * height; }
単純なゲッター/セッターおよびその他の単一行の関数を定義するには、矢印表記法が最適であることがわかります。
フィールド、ゲッター、セッター
プロパティを操作するために、Dartは
object.someProperty
形式の標準構文を使用します。 Dartでは、クラスフィールドの呼び出しのように見えるメソッドを定義できますが、同時に任意のコードを実行できます。 他の言語と同様に、そのようなメソッドはゲッターおよびセッターと呼ばれます。
class Rectangle { num left, top, width, height; num get right() => left + width; set right(num value) => left = value - width; num get bottom() => top + height; set bottom(num value) => top = value - height; Rectangle(this.left, this.top, this.width, this.height); }
4つの「実際の」プロパティ(
left
、
top
、
width
、
height
と、ゲッターとセッターの形式の2つの論理プロパティ(
right
と
bottom
を持つ
Rectangle
クラスがあり
bottom
。 クラスを使用する場合、自然フィールドとゲッターおよびセッターの間に明らかな違いはありません。
var rect = new Rectangle(3, 4, 20, 15); print(rect.left); print(rect.bottom); rect.top = 6; rect.right = 12;
フィールドとゲッター/セッターの境界を消去することは、言語の基本的な特性の1つです。 フィールドを「魔法の」ゲッターとセッターのセットと考えるのが最善です。 したがって、継承されたゲッターを自然フィールドでオーバーライドすることもでき、その逆も可能です。 インターフェースにゲッターが必要な場合、実装では同じ名前とタイプのフィールドを指定するだけです。 フィールドが変更可能(
final
はない)の場合、インターフェースに必要なセッターを作成できます。
実際には、これは、JavaやC#のように、クラスフィールドをゲッターとセッターの束で慎重に分離する必要がないことを意味します。 パブリックプロパティを宣言してください。 変更されないようにするには、
final
キーワードを使用します。
後で、検証などを行う必要が生じた場合は、このフィールドをいつでもゲッターとセッターに置き換えることができます。 たとえば、
Rectangle
クラスのサイズを常に負ではないようにします。
class Rectangle { num left, top; num _width, _height; num get width() => _width; set width(num value) { if (value < 0) throw 'Width cannot be negative.'; _width = value; } num get height() => _height; set height(num value) { if (value < 0) throw 'Height cannot be negative.'; _height = value; } num get right() => left + width; set right(num value) => left = value - width; num get bottom() => top + height; set bottom(num value) => top = value - height; Rectangle(this.left, this.top, this._width, this._height); }
コードを他の場所で変更する必要なく、クラスに検証を追加しました。
トップレベルの定義
Dartは純粋なオブジェクト指向言語です。 変数に配置できるものはすべてオブジェクト(可変の「プリミティブ」ではない)であり、各オブジェクトはクラスのインスタンスです。 ただし、これは「独断的な」OOPではありません。すべてをクラスに入れる必要はありません。 代わりに、トップレベルで変数、関数、さらにゲッターとセッターを定義できます。
num abs(num value) => value < 0 ? -value : value; final TWO_PI = Math.PI * 2.0; int get today() { final date = new DateTime.now(); return date.day; }
すべてのクラスまたはオブジェクトをJavaScriptのように配置する必要のない言語でも、名前の競合を回避するためにそうするのが一般的です。異なる場所でのグローバル定義が競合する可能性があります。 これに対処するために、Dartには、あいまいさを避けるためにプレフィックスを追加して他のファイルから定義をインポートできるライブラリシステムがあります。 そのため、クラス内で定義を隠す必要はありません。
この機能がライブラリの作成方法にどのように影響するかを引き続き調査しています。 ほとんどのコードは、
Math
などのクラス内に定義を配置します。 これが他の言語に根ざした習慣であるとか、Dartの優れたプログラミング手法であるとは言い難いです。 この分野では、他の開発者からのフィードバックが本当に必要です。
トップレベルの定義の使用例がいくつかあります。 まず第一に、これは
main()
です。 DOMを使用する場合、「変数」
document
と
window
はトップレベルで定義されたゲッターです。
行と補間
Dartにはいくつかのタイプの文字列リテラルがあります。 二重引用符と単一引用符、および複数行リテラルの三重引用符を使用できます。
'I am a "string"' "I'm one too" '''I'm on multiple lines ''' """ As am I """
複数の行を1つに結合するには、連結を使用できます。
var name = 'Fred'; var salutation = 'Hi'; var greeting = salutation + ', ' + name;
ただし、補間はよりクリーンで高速になります。
var name = 'Fred'; var salutation = 'Hi'; var greeting = '$salutation, $name';
ドル記号(
$
)の代わりに変数名が続き、変数値が置換されます(文字列でない場合、
toString()
メソッドが呼び出されます)。 中括弧内に式を置くことができます:
var r = 2; print('The area of a circle with radius $r is ${Math.PI * r * r}');
オペレーター
Dartは、C、Java、および他の同様の言語と同じ優先順位で同じ演算子を使用します。 期待どおりに動作します。 ただし、内部実装には独自の特性があります。 Dartでは、
1 + 2
の形式の演算子を持つ式は、メソッドを呼び出すための単なる構文上の砂糖です。 言語の観点から見ると、この例は
1.+(2)
ように見え
1.+(2)
。
これは、独自のタイプを作成することにより、ほとんどの演算子をオーバーロードできることを意味します。 たとえば、
Vector
クラス:
class Vector { num x, y; Vector(this.x, this.y); operator +(Vector other) => new Vector(x + other.x, y + other.y); }
これで、通常の構文を使用してベクトルを追加できます。
var position = new Vector(3, 4); var velocity = new Vector(1, 2); var newPosition = position + velocity;
ただし、この機会を悪用すべきではありません。 あなたが柱にぶつからないように、私たちはあなたに車の鍵を渡します。
実際には、定義する型が実世界で演算子を使用している場合、複素数、ベクトル、行列などの標準演算子を再定義する価値があることを示す良い兆候です。 他のすべての場合では、演算子をオーバーロードすることはほとんど価値がありません。 オーバーロードされた演算子を持つ型は通常、不変でなければなりません。
演算子はメソッド呼び出しであるため、最初は非対称であることに注意してください。 メソッド検索は、常に左の引数に対して行われます。 したがって、
a + b
を記述
a + b
とき、演算の意味は
a + b
のタイプに依存します。
平等
この演算子セットには特別な注意を払う必要があります。 Dartには、
==
と
!=
And
===
と
!==
2組の等価演算子があり
!==
。 JavaScriptプログラマーには馴染みがあるように見えますが、ここでは動作が少し異なります。
==
および
!=
、等価性をチェックするのに役立ちます。 使用する時間の99%。 JavaScriptとは異なり、暗黙的な変換は行わないため、より予測可能な動作をします。 それらを使用することを恐れないでください! Javaとは異なり、同値関係が定義されているすべてのタイプで機能します。 これ以上
someString.equals("something")
ありません。
理にかなっている場合は、タイプに対して
==
をオーバーロードできます。
!=
をオーバーロードする必要はありません
==
から自動的に削除します。
演算子の2番目のペア、
===
および
!==
、同一性を確認するのに役立ちます。
a === b
は、
b
と
b
がメモリ内で同じオブジェクトである場合にのみ
true
を返し
true
。 実際にそれらを使用する必要はほとんどありません。 デフォルトでは、
==
は、型に同値関係が定義されていない場合に
===
依存するため、ユーザーオーバーライド
==
をバイパスする場合にのみ
===
が必要になります。