「デザインパターン」カテゴリーアーカイブ

【デザインパターン】Chain of Responsibilityパターン

Chain of Responsibilityのサンプルコードです。

package org.example.chainofresponsibility;

public class Question {
    public int level;
    public Question(int level){
        this.level = level;
    }
}
package org.example.chainofresponsibility;

public abstract class Handler {
    private Handler next;

    public Handler setNext(Handler next)
    {
        this.next = next;
        return next;
    }

    public final void request(Question question)
    {
        if(judge(question)) {
            // 処理完了
        } else if(next != null) {
            next.request(question);
        } else {
            // 処理不可能
        }
    }

    protected abstract boolean judge(Question question);
}
package org.example.chainofresponsibility;

public class ConcreteHandler1 extends Handler {
    @Override
    protected boolean judge(Question question) {
        if(question.level <= 1) {
            return true;
        } else {
            return false;
        }
    }
}
package org.example.chainofresponsibility;

public class ConcreteHandler2 extends Handler{
    @Override
    protected boolean judge(Question question) {
        if(question.level <= 2) {
            return true;
        } else {
            return false;
        }
    }
}
package org.example.chainofresponsibility;

public class Main {
    public static void main(String[] args)
    {
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();

        handler1.setNext(handler2);

        Question question = new Question(1);

        handler1.request(question);
    }
}

Chain of Responsibilityは処理を行う人(Handler)を数珠つなぎに配列しておき、リクエスト(Question)に対して先頭のHandlerから処理が可能かどうかを判定(judge)し、処理できない物であれば後ろのHandlerにまる投げする、という仕組みです。

なので、あらかじめHandlerを継承しているConcreteHandlerを作成して数珠つなぎを作っておく必要があります。

ConcreteHandlerにてjudge=trueならば、渡されたquestionを適切に処理し、judge=falseならば、そのquestionを後ろのConcreteHanderに渡します。

デザインパターン】Proxyパターン

Proxyパターンのサンプルコードです。

package org.example.proxy;

public interface Subject {
    void request1();
    void request2();
}
package org.example.proxy;

public class RealSubject implements Subject {
    @Override
    public void request1() {
        // 時間のかかる処理
    }

    @Override
    public void request2() {
        // 時間のかかる処理
    }
}
package org.example.proxy;

public class Proxy implements Subject{
    private RealSubject real;

    @Override
    public synchronized void request1() {
        realize();
        real.request1();
    }

    @Override
    public synchronized void request2() {
        realize();
        real.request2();
    }

    private void realize() {
        if(real == null) {
            real = new RealSubject();
        }
    }
}
package org.example.proxy;

public class Main {
    public static void main(String[] args) {
        Subject subject = new Proxy();
        subject.request1();
        subject.request2();
    }
}

Proxyパターンは時間のかかる処理を実行する際、クラスを一つ噛ませて一部の処理を代行(Proxy)するパターンです。

上のサンプルコードではRealSubjectが時間のかかる処理を行い、それを実行する前にProxyが実行されます。

ProxyとReadSubjectはSubjectという共通のインターフェースを持っています。

この例では、「直接ReadSubjectを実行してもいいんじゃね?」って思いますが、

実際に利用されるパターンとしては、ReadSubjectでは時間のかかる処理のみを実装し、それ以外の処理をProxyで処理させる、というのが一般的のようです。

デザインパターン】Flyweightパターン

Flyweightパターンのサンプルコードです。

package org.example.flyweight;

public class Flyweight {
    public void method1() {

    }

    public void method2() {

    }
}
package org.example.flyweight;

import java.util.HashMap;
import java.util.Map;

public class FlyweightFactory {
    private Map<Integer, Flyweight> pool = new HashMap<>();
    private static FlyweightFactory factory = new FlyweightFactory();

    private FlyweightFactory() {

    }

    public static FlyweightFactory getFactory() {
        return factory;
    }

    public Flyweight getFlyweight(int key) {
        Flyweight result;
        boolean exist = pool.containsKey(key);
        if(exist == true) {
            result = pool.get(key);
        } else {
            result = new Flyweight();
            pool.put(key, result);
        }

        return result;
    }
}
package org.example.flyweight;

public class Main {
    public static void main(String[] args) {
        FlyweightFactory factory = FlyweightFactory.getFactory();
        Flyweight object = factory.getFlyweight(0);
        object.method1();
        object.method2();
    }
}

Flyweightパターンは一度作成したインスタンスを保持して再利用することによってインスタンス作成時の負荷を軽減させるパターンです。

サンプルコードではFlyweightのオブジェクトをFlyweightFactory内のpoolに保持し、一度使用したものはpoolから取得するようにしています。

FlyweightFactoryはSingletonパターンで使用するのが一般的です。

poolは、サンプルではHashMapを使用しましたが、Collectionのインターフェースを持つクラス(Listなど)でも使用できます。

このあたりは実際の設計に合わせて実装していくことになると思います。

デザインパターン】Facadeパターン

Facadeパターンのサンプルコードです。

package org.example.facade;

public class Class1 {
    public void method1()
    {

    }
}
package org.example.facade;

public class Class2 {
    public void method2()
    {

    }
}
package org.example.facade;

public class Facade {
    Class1 class1;
    Class2 class2;

    public Facade()
    {
        class1 = new Class1();
        class2 = new Class2();
    }
    public void function()
    {
        class1.method1();
        class2.method2();
    }
}
package org.example.facade;

public class Main {
    public static void main(String[] args)
    {
        Facade facade = new Facade();
        facade.function();
    }
}

Facadeパターンは、様々な機能を持つクラスの処理を、Facadeクラスの窓口に一本化するパターンです。

よく利用する一連の処理をFacadeのメソッドとして定義することで、扱いが簡単になります。

もしかしたら、デザインパターンを意識しなくてもやっているかもしれない。

デザインパターン】Decoratorパターン

Decoratorパターンのサンプルコードです。

package org.example.decorator;

public abstract class Component {
    public abstract void method1();
    public abstract void method2();
}
package org.example.decorator;

public class ConcreteComponent extends Component {
    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }
}
package org.example.decorator;

public abstract class Decorator extends Component {
    protected Component component;

    protected Decorator(Component component) {
        super();
        this.component = component;
    }
}
package org.example.decorator;

public class ConcreteDecorator extends Decorator {
    protected ConcreteDecorator(Component component) {
        super(component);
    }

    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }
}
package org.example.decorator;

public class Main {
    public static void main(String[] args)
    {
        Component component = new ConcreteComponent();
        Component instance = new ConcreteDecorator(component);
        instance.method1();
        instance.method2();

        Component component1 = new ConcreteDecorator(new ConcreteDecorator(component));
        component1.method1();
        component1.method2();
    }
}

このパターンでは、中身(ConcreteComponent)と装飾(ConcreteDecorator)が存在し、Componentという共通のインターフェースを持っています。

機能を追加する場合はDecoreterを継承したクラスを作成し、機能を拡張することができます。

ConcreteDecoratorのコンストラクタにはConcreteComponentを渡すこともできますし、すでに作成したConcreteDecoratorを渡すこともでき、それによって再帰的な処理をすることもできます。

しかし、クラスの関係が複雑になったりするので、クラス図による整理が必要になりますね。

【デザインパターン】Compositeパターン

Compositeパターンのサンプルコードです。

package org.example.composite;

public abstract class Component {
    public abstract void method1();
    public abstract void method2();
    protected void add(Component component){}
}
package org.example.composite;

public class Leaf extends Component{
    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }
}
package org.example.composite;

import java.util.ArrayList;
import java.util.List;

public class Composite extends Component{
    private List<Component> ComponentList = new ArrayList<>();

    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }

    @Override
    public void add(Component component) {
        ComponentList.add(component);
    }
}
package org.example.composite;

public class Main {
    public static void main(String[] args) {
        Component parent1 = new Composite();
        Component parent2 = new Composite();
        Component leaf1 = new Leaf();
        Component leaf2 = new Leaf();

        parent2.add(leaf1);
        parent2.add(leaf2);
        parent1.add(parent2);
    }
}

Compositeパターンは木構造を構成してオブジェクトを管理し、再帰的に使用したい場合に使用します。

Compositeクラスが木構造の親クラス、Leafクラスが木構造の末端のクラスです。どちらもComponentクラスを継承します。

Leafクラスでも使用するため、Componentクラスのaddメソッドはprotectedの処理無しで作成します。

実際に追加する処理はCompositeクラスのaddメソッドをオーバーライドして作成します。

【デザインパターン】Bridgeパターン

Bridgeパターンのサンプルコードです。

package org.example.bridge;

public abstract class Implementor {
    public abstract void implMethodX();
    public abstract void implMethodY();
}
package org.example.bridge;

public class concreteImplementorA extends Implementor{
    @Override
    public void implMethodX() {

    }

    @Override
    public void implMethodY() {

    }
}
package org.example.bridge;

public class concreteImplementorB extends Implementor{
    @Override
    public void implMethodX() {

    }

    @Override
    public void implMethodY() {

    }
}
package org.example.bridge;

public class Abstraction {
    Implementor implementor;

    public Abstraction(Implementor implementor)
    {
        this.implementor = implementor;
    }

    public void method1()
    {

    }

    public void method2()
    {

    }
}
package org.example.bridge;

public class RefileAbstraction extends Abstraction{
    public RefileAbstraction(Implementor implementor) {
        super(implementor);
    }

    @Override
    public void method1()
    {

    }

    @Override
    public void method2()
    {

    }

    public void refineMethodA()
    {

    }

    public void refineMethodB()
    {

    }
}
package org.example.bridge;

public class Main {
    public static void main(String[] args)
    {
        RefileAbstraction refileAbstraction1 = new RefileAbstraction(new concreteImplementorA());
        refileAbstraction1.method1();
        refileAbstraction1.method2();
        refileAbstraction1.refineMethodA();
        refileAbstraction1.refineMethodB();

        RefileAbstraction refileAbstraction2 = new RefileAbstraction(new concreteImplementorB());
        refileAbstraction2.method1();
        refileAbstraction2.method2();
        refileAbstraction2.refineMethodA();
        refileAbstraction2.refineMethodB();
    }
}

Bridgeパターンは、処理を機能(Abstraction)と実装(Implementor)に分けてコーディングするパターンです。

処理を拡張する場合はImplementorを継承してconcreteImplementorクラスを作成、機能側にそのインスタンスを渡すイメージです。

【デザインパターン】Adapterパターン

Adapterパターンのコード例です。

Adapterには継承を使ったパターンと委譲を使ったパターンがあります。

まずは継承を使用したパターン。

package org.example.adapter;

public class Adaptee {
    public void methodA()
    {

    }

    public void methodB()
    {

    }
}
package org.example.adapter;

public interface Target {
    public void targetMethod1();
    public void targetMethod2();
}
package org.example.adapter;

public class Adapter extends Adaptee implements Target{
    public void targetMethod1() {
        methodA();
    }

    public void targetMethod2() {
        methodB();
    }
}
package org.example.adapter;

public class Main {
    public static void main(String[] args)
    {
        Adaptee adaptee = new Adaptee();
        adaptee.methodA();
        adaptee.methodB();

        Adapter adapter = new Adapter();
        adapter.targetMethod1();
        adapter.targetMethod2();
    }
}

そして、委譲を使用したパターン。

package org.example.adapter;

public class Adapter implements Target{
    Adaptee adaptee;

    public Adapter()
    {
        adaptee = new Adaptee();
    }

    public void targetMethod1() {
        adaptee.methodA();
    }

    public void targetMethod2() {
        adaptee.methodB();
    }
}

このパターンは元々存在していたクラスAdapteeを使用したいけど、インターフェースが合わない、と言う場合、新しいクラスAdapterを作成して使用できるようにした、というパターンです。

継承と委譲がありますが、自分としては委譲の方がしっくりきます。

【デザインパターン】Singletonパターン

Singletonパターンのコード例です。

package org.example.singleton;

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton() {

    }

    public static Singleton getInstance() {
        return singleton;
    }

    public void method1() {

    }
}
package org.example.singleton;

public class Main {
    public static void main(String[] args) {
        Singleton obj = Singleton.getInstance();
        obj.method1();
    }
}

Singletonはプログラム上にインスタンスが一つしか存在しない場合に使用されます。

インスタンスの取得はgetInstance()で取得して使用します。

このインスタンスは常に同じもので、一つのインスタンスをみんなで使い回すというイメージですね。

よく使われるパターンです。

【デザインパターン】Prototypeパターン

Prototypeパターンのコート例です。

package org.example.prototype;

import java.io.Closeable;

public interface Prototype extends Cloneable {
    public void method1();
    public void method2();
    public Prototype createClone();
}
package org.example.prototype;

import java.io.IOException;

public class ConcretePrototype implements Prototype {
    public ConcretePrototype() {
        super();
    }

    @Override
    public void method1() {

    }

    @Override
    public void method2() {

    }

    @Override
    public Prototype createClone() {
        Prototype prototype = null;

        try {
            prototype = (Prototype)clone();
        }catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototype;
    }
}
package org.example.prototype;

public class Main {
    public static void main(String[] args){
        Prototype PrototypeObject = new ConcretePrototype();
        Prototype cloneObject = PrototypeObject.createClone();
        cloneObject.method1();
        cloneObject.method2();
    }
}

Prototypeインターフェースを実装したConcretePrototypeを作成し、以後、createClone()メソッドを使用して複製したものを使用する、という使い方をします。

Cloneableを実装しないとClone()実行時にCloneNotSupportedExceptionが発生するみたいです。

これ、Java以外にもC#とかにも無いっすかね?

※C#ではICloneableを実装すると使えるみたいです。

あと、Cloneは参照型のメンバーがいた場合、複製したObjectも複製前と同じ参照先を参照するらしいです。

完全に複製するには、CloneをOverrideして、参照型のメンバーをさらにCloneする、というやり方が必要になるようです。

Cloneを使用する場合はその当たり気をつけないといけないですね。