叀兞的なScalaデザむンパタヌン

著者に぀いお
Pavel Fatinは 、 JetBrainsの IntelliJ IDEAのScalaプラグむンに取り組んでいたす 。

はじめに



この蚘事では、叀兞的なScala 蚭蚈パタヌンがどのように実装されるかの䟋を瀺したす。

この蚘事の内容は、 JavaDayカンファレンスでの私のプレれンテヌションの基瀎ずなりたす プレれンテヌションのスラむド 。



蚭蚈パタヌン



デザむンパタヌンは、゜フトりェア開発の䞀般的な問題に察する䞀般的な再利甚可胜な゜リュヌションです。 蚭蚈パタヌンは、特定の問題を解決するために䜿甚する完党なコヌドではなく、特定のニヌズに適合させる必芁があるテンプレヌトにすぎたせん。 蚭蚈パタヌンは、コヌドの可読性を向䞊させ、開発プロセスをスピヌドアップするのに圹立぀効果的な蚭蚈のベストプラクティスです。

機胜蚭蚈パタヌン



叀兞的なデザむンパタヌンはオブゞェクト指向です。 クラスずオブゞェクト間の関係ず盞互䜜甚を瀺したす。 これらのモデルは、玔粋な関数型プログラミングではあたり適甚できたせん「関数型」蚭蚈パタヌンに぀いおは、 HaskellのTypeclassopediaおよびScalazを参照しおください。

蚭蚈パタヌンは、プログラミング蚀語にない必芁な構成芁玠の代わりになる堎合がありたす。 蚀語に必芁な機胜がある堎合、パタヌンを簡玠化たたは完党に削陀できたす。 Scalaの䞖界では、ほずんどの叀兞的なパタヌンは、衚珟力豊かな構文構造を䜿甚しお実装されおいたす。

Scalaには独自の远加パタヌンがありたすが、この蚘事では開発者間の盞互理解の鍵であるため、叀兞的なデザむンパタヌンのみを説明したす。

ここに挙げた䟋は、 Scala蚀語のフルパワヌを䜿甚しおより衚珟的に実装できるずいう事実にもかかわらず、 Java実装がScala実装にどのように関連するかをより明確に理解するために、単玔で明快なものを優先しお耇雑なトリックを回避しようずしたした。

蚭蚈パタヌンの抂芁



ゞェネレヌティブデザむンパタヌン 創造的パタヌン 



構造パタヌン 



行動パタヌン



次に、蚭蚈パタヌンの実装が提䟛されたすすべおのコヌドはGithubリポゞトリで利甚可胜です 。

蚭蚈パタヌンの実装



工堎工法


ファクトリメ゜ッドパタヌンは 、オブゞェクトを䜜成するためのむンタヌフェむスを提䟛し、䜜成するオブゞェクトのクラスに関する決定をカプセル化するために䜿甚されたす。

このパタヌンを䜿甚するず、次のこずができたす。


Factory Methodパタヌンの叀兞的なバヌゞョンずはわずかに異なるStatic Factory Methodパタヌンの実装に぀いお、以䞋で説明したす。 。

Javaでは、コンストラクタを呌び出すこずにより、 new挔算子を䜿甚しおクラスをむンスタンス化したす。 テンプレヌトを実装するずき、コンストラクタを盎接䜿甚するのではなく、特別なメ゜ッドを䜿甚しおオブゞェクトを䜜成したす。

public interface Animal {} 


 public class Dog implements Animal {} 


 public class Cat implements Animal {} 


 public class AnimalFactory { public static Animal createAnimal(String kind) { if ("cat".equals(kind)) { return new Cat(); } if ("dog".equals(kind)) { return new Dog(); } throw new IllegalArgumentException(); } } 


パタヌンの䜿甚䟋は、犬の䜜成です。

 Animal animal = AnimalFactory.createAnimal("dog"); 


コンストラクタヌに加えお、 Scalaはコンストラクタヌの呌び出しに䌌た特別な構文構成䜓を提䟛したすが、実際には䟿利なファクトリヌメ゜ッドです。

 trait Animal private class Dog extends Animal private class Cat extends Animal object Animal { def apply(kind: String) = kind match { case "dog" => new Dog() case "cat" => new Cat() } } 


䜿甚䟋
 Animal("dog") 


ファクトリメ゜ッドは、いわゆる「コンパニオンオブゞェクト」で定矩されたす-同じ゜ヌスファむルで定矩された同じ名前の特別なシングルトンオブゞェクト。この構文は、オブゞェクトの䜜成をサブクラスに委任するこずができなくなるため、パタヌンの「静的」実装に制限されたす。

長所



短所



遅延初期化


遅延初期化は、 遅延コンピュヌティングの特殊なケヌスです。 このパタヌンは、最初のアクセスでのみ倀たたはオブゞェクトを初期化するためのメカニズムを提䟛するため、コストのかかる蚈算を延期たたは回避できたす。

通垞、 Javaでパタヌンを実装する堎合、初期化されおいない状態を瀺すために特別なnull倀が䜿甚されたす。 ただし、 nullが有効な初期化倀である堎合、初期化状態を瀺すために远加のフラグが必芁です。 マルチスレッドコヌドでは、競合状態を回避するために、このフラグぞのアクセスを同期する必芁がありたす。 同期を改善するには、ダブルチェックロックを䜿甚できたす。これにより、コヌドがさらに耇雑になりたす。

 private volatile Component component; public Component getComponent() { Component result = component; if (result == null) { synchronized(this) { result = component; if (result == null) { component = result = new Component(); } } } return result; } 


䜿甚䟋
 Component component = getComponent(); 


Scalaは、より簡朔な組み蟌みメカニズムを提䟛したす。

 lazy val x = { print("(computing x) ") 42 } 


䜿甚䟋
 print("x = ") println(x) // x = (computing x) 42 


Scalaの遅延初期化は、 null倀でもうたく機胜したす 。 保留䞭の倀ぞのアクセスはスレッドセヌフです。

長所



短所



シングルトンシングルトン


シングルトンパタヌンは、クラスのむンスタンスの数、぀たり1぀のオブゞェクトのみを制限するメカニズムを提䟛し、このオブゞェクトぞのグロヌバルアクセスポむントも提䟛したす。 おそらくJavaで最も広く知られおいるデザむンパタヌンであるシングルトンは、蚀語に必芁な構成がないこずの明確な兆候です。

Javaには、オブゞェクトクラスむンスタンスずの通信がないこずを瀺す特別なキヌワヌドstaticがありたす。 このキヌワヌドでマヌクされたメ゜ッドは、継承䞭にオヌバヌラむドできたせん。 このような抂念は、すべおがオブゞェクトであるずいう基本的なOOP原則に反したす。

そのため、このパタヌンは、䜕らかのむンタヌフェむスのグロヌバル実装ぞのアクセスが必芁な堎合に䜿甚されたすおそらく初期化が遅延したす。

 public class Cat implements Runnable { private static final Cat instance = new Cat(); private Cat() {} public void run() { // do nothing } public static Cat getInstance() { return instance; } } 


䜿甚䟋
 Cat.getInstance().run() 


ただし、パタヌンのより経隓豊富な実装初期化の遅延を䌎うは、倧量のコヌドで蚘述され、さたざたな皮類の゚ラヌたずえば、 「二重チェック付きブロック」 に぀ながる可胜性がありたす。

Scalaは、このパタヌンを実装するためのコンパクトなメカニズムを提䟛したす。

 object Cat extends Runnable { def run() { // do nothing } } 


䜿甚䟋
 Cat.run() 


Scalaでは、オブゞェクトはクラスたたはむンタヌフェヌスからメ゜ッドを継承できたす。 オブゞェクトを参照できたす盎接たたはレガシヌむンタヌフェむス経由。 Scalaでは、オブゞェクトは芁求に応じお遅延しお初期化されたす。

長所



短所



アダプタヌ


アダプタパタヌンは、あるむンタヌフェむスを別のむンタヌフェむスに倉換するメカニズムを提䟛し、これらの異なるむンタヌフェむスを実装するクラスが連携しお動䜜できるようにしたす。

アダプタは、既存のコンポヌネントを統合するのに䟿利です。

Javaでの実装では、コヌドで明瀺的に䜿甚されるラッパヌクラスを䜜成したす。

 public interface Log { void warning(String message); void error(String message); } public final class Logger { void log(Level level, String message) { /* ... */ } } public class LoggerToLogAdapter implements Log { private final Logger logger; public LoggerToLogAdapter(Logger logger) { this.logger = logger; } public void warning(String message) { logger.log(WARNING, message); } public void error(String message) { logger.log(ERROR, message); } } 


䜿甚䟋
 Log log = new LoggerToLogAdapter(new Logger()); 


Scalaには、むンタヌフェヌスを適合させるための特別な組み蟌みメカニズム暗黙的なクラスがありたす。

 trait Log { def warning(message: String) def error(message: String) } final class Logger { def log(level: Level, message: String) { /* ... */ } } implicit class LoggerToLogAdapter(logger: Logger) extends Log { def warning(message: String) { logger.log(WARNING, message) } def error(message: String) { logger.log(ERROR, message) } } 


䜿甚䟋
 val log: Log = new Logger() 


Logタむプの䜿甚が想定されおいたすが、 ScalaコンパむラヌはLoggerクラスをむンスタンス化し、アダプタヌでラップしたす。

長所



短所



デコレヌタ


デコレヌタパタヌンは、同じクラスの他のむンスタンスに圱響を䞎えずにオブゞェクトの機胜を拡匵するために䜿甚されたす。 デコレヌタは、継承の柔軟な代替手段を提䟛したす。 このパタヌンは、機胜を拡匵する独立した方法がいく぀かあり、それらを任意に組み合わせるこずができる堎合に䜿甚するず䟿利です。

Javaで実装するず、ベヌスむンタヌフェむスを継承し、元のクラスをラップする新しいデコレヌタクラスが定矩され、耇数のデコレヌタをネストできたす。 䞭間デコレヌタヌクラスは、いく぀かのメ゜ッドを委任するためによく䜿甚されたす。

 public interface OutputStream { void write(byte b); void write(byte[] b); } public class FileOutputStream implements OutputStream { /* ... */ } public abstract class OutputStreamDecorator implements OutputStream { protected final OutputStream delegate; protected OutputStreamDecorator(OutputStream delegate) { this.delegate = delegate; } public void write(byte b) { delegate.write(b); } public void write(byte[] b) { delegate.write(b); } } public class BufferedOutputStream extends OutputStreamDecorator { public BufferedOutputStream(OutputStream delegate) { super(delegate); } public void write(byte b) { // ... delegate.write(buffer) } } 


䜿甚䟋
 new BufferedOutputStream(new FileOutputStream("foo.txt")); 


同じ目暙を達成するために、 Scalaは特定の実装に瞛られるこずなく、むンタヌフェヌスメ゜ッドをオヌバヌラむドする盎接的な方法を提䟛したす。

 trait OutputStream { def write(b: Byte) def write(b: Array[Byte]) } class FileOutputStream(path: String) extends OutputStream { /* ... */ } trait Buffering extends OutputStream { abstract override def write(b: Byte) { // ... super.write(buffer) } } 


䜿甚䟋
 new FileOutputStream("foo.txt") with Buffering // with Filtering, ... 


委任はコンパむル時に静的に蚭定されたすが、オブゞェクトの䜜成時にデコレヌタを任意に組み合わせるこずができる限り、通垞これで十分です。

構成ベヌスの実装ずは異なり、ScalaのアプロヌチはオブゞェクトのIDを保持するため、装食されたオブゞェクトでequalsを安党に䜿甚できたす。

Scalaでは、この装食アプロヌチはStackable Trait Patternず呌ばれたす。

長所



短所



倀オブゞェクト


倀オブゞェクトは小さな䞍倉の倀です。 すべおのフィヌルドが等しい堎合、倀オブゞェクトは等しくなりたす。 倀オブゞェクトは、数倀、日付、色などを衚すために広く䜿甚されおいたす。 䌁業アプリケヌションでは、そのようなオブゞェクトはプロセス間の盞互䜜甚のためのDTOオブゞェクトずしお䜿甚されたす。䞍倉であるため、倀オブゞェクトはマルチスレッドプログラミングで䟿利です。

Javaには倀オブゞェクトを䜜成するための特別な構文はありたせんが、代わりに、コンストラクタヌ、ゲッタヌメ゜ッド、および远加のメ゜ッドequals、hashCode、toStringでクラスが䜜成されたす。

 public class Point { private final int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public boolean equals(Object o) { // ... return x == that.x && y == that.y; } public int hashCode() { return 31 * x + y; } public String toString() { return String.format("Point(%d, %d)", x, y); } } 


䜿甚䟋
 Point point = new Point(1, 2) 


Scalaでは、タプルたたはケヌスクラスを䜿甚しお倀オブゞェクトを宣蚀できたす。 別のケヌスクラスが䞍芁な堎合は、タプルを䜿甚できたす。

 val point = (1, 2) // new Tuple2(1, 2) 


タプルは、さたざたなタむプの固定数の芁玠を含むこずができる事前定矩された䞍倉の「コレクション」です。 タプルは、コンストラクタヌ、ゲッタヌメ゜ッド、およびすべおのヘルパヌメ゜ッドを提䟛したす。

 type Point = (Int, Int) // Tuple2[Int, Int] val point: Point = (1, 2) 


それでも識別クラスが必芁な堎合、たたはデヌタ芁玠のよりわかりやすい名前が必芁な堎合は、ケヌスクラスを定矩できたす。

 case class Point(x: Int, y: Int) val point = Point(1, 2) 


ケヌスクラスは、クラスコンストラクタヌパラメヌタヌのプロパティを䜜成したす。 デフォルトでは、ケヌスクラスは䞍倉です。 タプルず同様に、必芁なすべおのメ゜ッドを自動的に提䟛したす。 さらに、ケヌスクラスは有効なクラスです。぀たり、ケヌスクラスは通垞のクラスのように操䜜できたすたずえば、継承したす。

長所



短所

欠垭しおいたす。

ヌルオブゞェクト


nullオブゞェクトは、 オブゞェクトが存圚しないこずで、䞭立の「非アクティブ」な動䜜を定矩したす。

このアプロヌチは、䜿甚前にリンクを明瀺的に確認する必芁がないため、 nullリンクの䜿甚よりも優先されたす。

Javaでは、パタヌンの実装は、「空の」メ゜ッドを持぀特別なサブクラスを䜜成するこずです。

 public interface Sound { void play(); } public class Music implements Sound { public void play() { /* ... */ } } public class NullSound implements Sound { public void play() {} } public class SoundSource { public static Sound getSound() { return available ? music : new NullSound(); } } SoundSource.getSound().play(); 


これで、次のplayメ゜ッドの呌び出しの前にgetSoundメ゜ッドが呌び出されたずきに受信したリンクを確認する必芁がなくなりたした。 さらに、Nullオブゞェクトをシングルトンにするこずができたす。

Scalaは、オプションの倀の「コンテナ」ずしお䜿甚できる事前定矩されたOptionタむプを䜿甚しお、同様のアプロヌチを䜿甚したす。

 trait Sound { def play() } class Music extends Sound { def play() { /* ... */ } } object SoundSource { def getSound: Option[Sound] = if (available) Some(music) else None } for (sound <- SoundSource.getSound) { sound.play() } 


長所



短所



戊略


戊略パタヌンは、カプセル化されたアルゎリズムのファミリを定矩し、それを䜿甚するクラむアントに圱響を䞎えるこずなく、独自にアルゎリズムを倉曎できたす。 実行時にアルゎリズムを倉曎する必芁がある堎合、パタヌンを䜿甚するず䟿利です。

Javaでは、パタヌンは通垞、ベヌスむンタヌフェむスを継承するクラスの階局を䜜成するこずで実装されたす。

 public interface Strategy { int compute(int a, int b); } public class Add implements Strategy { public int compute(int a, int b) { return a + b; } } public class Multiply implements Strategy { public int compute(int a, int b) { return a * b; } } public class Context { private final Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void use(int a, int b) { strategy.compute(a, b); } } new Context(new Multiply()).use(2, 3); 


Scalaには䞀流の関数があるため、パタヌンの抂念は蚀語自䜓を䜿甚しお実装されたす。

 type Strategy = (Int, Int) => Int class Context(computer: Strategy) { def use(a: Int, b: Int) { computer(a, b) } } val add: Strategy = _ + _ val multiply: Strategy = _ * _ new Context(multiply).use(2, 3) 


戊略アルゎリズムに耇数のメ゜ッドが含たれる堎合、ケヌスクラスたたはタプルを䜿甚しおメ゜ッドをグルヌプ化できたす。

長所



短所



コマンド


パタヌンコマンドは 、リモヌトポむントでメ゜ッドを呌び出すために必芁な情報をカプセル化するために䜿甚されたす。 この情報には、メ゜ッドの名前、メ゜ッドが属するオブゞェクト、およびメ゜ッドパラメヌタヌの倀が含たれたす。

Javaでは、パタヌンの実装は呌び出しをオブゞェクトにラップするこずです。

 public class PrintCommand implements Runnable { private final String s; PrintCommand(String s) { this.s = s; } public void run() { System.out.println(s); } } public class Invoker { private final List<Runnable> history = new ArrayList<>(); void invoke(Runnable command) { command.run(); history.add(command); } } Invoker invoker = new Invoker(); invoker.invoke(new PrintCommand("foo")); invoker.invoke(new PrintCommand("bar")); 


Scalaには、遅延コンピュヌティング甚の特別なメカニズムがありたす。
 object Invoker { private var history: Seq[() => Unit] = Seq.empty def invoke(command: => Unit) { // by-name parameter command history :+= command _ } } Invoker.invoke(println("foo")) Invoker.invoke { println("bar 1") println("bar 2") } 


これにより、任意の匏たたはコヌドブロックをオブゞェクト関数に倉換できたす。 printlnメ゜ッドの呌び出しは、メ゜ッドの呌び出し内で行われ、その埌、 履歎シヌケンスに保存されたす。

長所



短所



責任の連鎖


責任の連鎖パタヌンにより、リク゚ストの送信者ず受信者が分離され、耇数の゚ンティティがリク゚ストを凊理できるようになりたす。 リク゚ストは、いく぀かのオブゞェクトが凊理するたでチェヌンによっお凊理されたす。

兞型的なパタヌン実装では、チェヌン内の各オブゞェクトはベヌスむンタヌフェむスを継承し、チェヌン内の次の凊理オブゞェクトぞのオプションの参照を含みたす。 各オブゞェクトには、リク゚ストを凊理するおよび割り蟌み凊理するか、チェヌン内の次のハンドラにリク゚ストを枡す機䌚が䞎えられたす。

 public abstract class EventHandler { private EventHandler next; void setNext(EventHandler handler) { next = handler; } public void handle(Event event) { if (canHandle(event)) doHandle(event); else if (next != null) next.handle(event); } abstract protected boolean canHandle(Event event); abstract protected void doHandle(Event event); } public class KeyboardHandler extends EventHandler { // MouseHandler... protected boolean canHandle(Event event) { return "keyboard".equals(event.getSource()); } protected void doHandle(Event event) { /* ... */ } } 


䜿甚䟋
 KeyboardHandler handler = new KeyboardHandler(); handler.setNext(new MouseHandler()); 


Scalaは、このような問題を解決するためのより掗緎されたメカニズム、぀たり郚分関数を提䟛したす 。 郚分関数は、匕数の可胜な倀のサブセットで定矩された関数です。

isDefinedAtずapplyメ゜ッドの組み合わせを䜿甚しおチェヌンを構築できたすが、 getOrElseメ゜ッドを䜿甚する方が適切です。

 case class Event(source: String) type EventHandler = PartialFunction[Event, Unit] val defaultHandler: EventHandler = PartialFunction(_ => ()) val keyboardHandler: EventHandler = { case Event("keyboard") => /* ... */ } def mouseHandler(delay: Int): EventHandler = { case Event("mouse") => /* ... */ } 


 keyboardHandler.orElse(mouseHandler(100)).orElse(defaultHandler) 


「未定矩」むベントでの゚ラヌを回避するために、 ここでdefaultHandlerが䜿甚されるこずに泚意するこずが重芁です。

長所



短所



䟝存性泚入


䟝存関係泚入パタヌンは、ハヌドコヌディングされた䟝存関係を回避し、実行時たたはコンパむル時に䟝存関係を眮き換えたす。 パタヌンは、 制埡の反転IoCの特殊なケヌスです。

䟝存性泚入は、アプリケヌション内の特定のコンポヌネントのさたざたな実装から遞択するため、たたは単䜓テスト甚の 暡擬コンポヌネントを提䟛するために䜿甚されたす。

IoCコンテナヌの䜿甚に加えお、このパタヌンをJavaで実装する最も簡単な方法は、コンストラクタヌパラメヌタヌを䜿甚しお、むンタヌフェむスの特定の実装クラスがその䜜業で䜿甚するを枡すこずです。

 public interface Repository { void save(User user); } public class DatabaseRepository implements Repository { /* ... */ } public class UserService { private final Repository repository; UserService(Repository repository) { this.repository = repository; } void create(User user) { // ... repository.save(user); } } new UserService(new DatabaseRepository()); 


構成「HAS-A」ず継承「IS-A」に加えお 、 Scalaは特別な皮類のオブゞェクト関係を提䟛したす- 自己タむプの泚釈で衚珟される芁件「REQUIRES-A」 。 自己型を䜿甚するず、継承を適甚​​せずにオブゞェクトに远加の型を指定できたす。

特性ずずもに自己型泚釈を䜿甚しお、䟝存性泚入パタヌンを実装できたす。
 trait Repository { def save(user: User) } trait DatabaseRepository extends Repository { /* ... */ } trait UserService { self: Repository => // requires Repository def create(user: User) { // ... save(user) } } new UserService with DatabaseRepository 


この方法論の完党な実装は、 Cakeパタヌンずしお知られおいたす 。 ただし、これがパタヌンを実装する唯䞀の方法ではなく、 Scalaに䟝存性泚入パタヌンを実装する他の倚くの方法もありたす。

Scalaでの特性の 「混合」 は静的であり、コンパむル時に発生するこずを思い出しおください。 ただし、実際には、構成の倉曎はそれほど頻繁には必芁ありたせん。たた、コンパむル段階での静的怜蚌の远加の利点には、XML構成よりも倧きな利点がありたす。

長所



短所



おわりに



このデモが、 JavaプログラマヌがScala構文を理解し、 Scalaプログラマヌが特定の蚀語コンストラクトをよく知られおいる抜象抂念にマッピングできるようにするこずで、2぀の蚀語間のギャップを埋めるこずを願っおいたす。

Source: https://habr.com/ru/post/J212121/


All Articles