【C++】【ゲーム開発】依存性逆転の原則~Factory Methodパターン導入

eye-catch C/C++
eye-catch

Factory Methodパターンで使ってみる

まずはProductに該当する抽象クラスです。

// ***************************************************
// 
// gadgets.hpp
//
// ***************************************************



#ifndef ERIS_DOMAIN_MODULES_FACTORY_GADGETS_HPP_
#define ERIS_DOMAIN_MODULES_FACTORY_GADGETS_HPP_


namespace modules {

    namespace factory {

        namespace abstract__ {

            // This is an abstract class that can only be used with create object class inheritance.
            class Gadgets {

            protected:
                unsigned __int32 GrHandle;

            public:
                virtual ~Gadgets() {}
                virtual void Use(void) = 0;

            };

        }  // namespace abstract__

    }  // namespace factory

}  // namespace modules

#endif // !ERIS_DOMAIN_MODULES_FACTORY_GADGETS_HPP_

unsigned __int32の箇所はあまり気にしなくていいです。(DxLib.hのゲーム構築で将来的に必要な実装で、今回のデザインパターンには関係ありません💦)

続いてFactoryクラスを抽象クラスで実装。

// ***************************************************
// 
// gadget_diecast.hpp
//
// ***************************************************



#ifndef ERIS_DOMAIN_MODULES_FACTORY_GADGET_DIECAST_HPP_
#define ERIS_DOMAIN_MODULES_FACTORY_GADGET_DIECAST_HPP_

// includes.
#include <string>


namespace modules {

    namespace factory {

        namespace abstract__ {

            class Gadgets;

            // A Gadgets generation abstract class for creating a specified objects, only inherits is supported.
            class GadgetDiecast {

            public:
                virtual Gadgets* create(std::string filepath) final;
                virtual Gadgets* createGadget(std::string filepath) = 0;

            };

        }  // namespace abstract__
        
    }  // namespace factory

}  // namespace modules

#endif // !ERIS_DOMAIN_MODULES_FACTORY_GADGET_DIECAST_HPP_
// ***************************************************
// 
// gadget_diecast.cpp
//
// ***************************************************



// includes.
#include "gadget_diecast.hpp"
#include "gadgets.hpp"


namespace modules {

    namespace factory {

        namespace abstract__ {

            Gadgets* GadgetDiecast::create(std::string filepath) {
                Gadgets *gadget = createGadget(filepath);
                return gadget;
            }

        }  // namespace abstract__

    }  // namespace factory

}  // namespace modules

これらを抽象クラスとして宣言しておき、createメソッドでGadgets(Product)オブジェクトを生成します。

filepathをcreateGadgetメソッドに渡すと、作成されたGadgetsオブジェクトが返却される仕組み。

createGadgetメソッドは次。

// ***************************************************
// 
// sprite_object.hpp
//
// ***************************************************



#ifndef ERIS_DOMAIN_MODULES_FACTORY_DRAWER_HPP_
#define ERIS_DOMAIN_MODULES_FACTORY_DRAWER_HPP_

// includes.
#include "gadgets.hpp"
#include <string>


namespace modules {

    namespace factory {

        // A class of common sprite graphic data.
        class SpriteObject : public abstract__::Gadgets {

        public:
            SpriteObject(std::string filepath);
            ~SpriteObject() {}
            void Use(void) override final;
            unsigned __int32 getOwner(void);

        };

    }  // namespace factory

}  // namespace modules

#endif // !ERIS_DOMAIN_MODULES_FACTORY_DRAWER_HPP_
// ***************************************************
// 
// sprite_object.cpp
//
// ***************************************************



// includes.
#include "sprite_object.hpp"
#include <string>
#include <DxLib.h>


namespace modules {

    namespace factory {

        SpriteObject::SpriteObject(std::string filepath) {
            GrHandle = LoadGraph(filepath.c_str());
        }
        
        // A class of common sprite graphic data.
        void SpriteObject::Use(void) {
            if (-1 != GrHandle) {
                DrawGraph(0, 0, GrHandle, FALSE);
            }
        }

        unsigned __int32 SpriteObject::getOwner(void) {
            return GrHandle;
        }

    }  // namespace factory

}  // namespace modules
// ***************************************************
// 
// graphic_gadget.hpp
//
// ***************************************************



#ifndef ERIS_DOMAIN_MODULES_FACTORY_GRAPHIC_MANAGER_HPP_
#define ERIS_DOMAIN_MODULES_FACTORY_GRAPHIC_MANAGER_HPP_

// includes.
#include "gadget_diecast.hpp"


namespace modules {

    namespace factory {

        class Gadgets;

        class GraphicGadget : public abstract__::GadgetDiecast {

        protected:
            abstract__::Gadgets* createGadget(std::string filepath) override;

        };

    }  // namespace factory

}  // namespace modules

#endif // !ERIS_DOMAIN_MODULES_FACTORY_GRAPHIC_MANAGER_HPP_
// ***************************************************
// 
// graphic_gadget.cpp
//
// ***************************************************



// includes.
#include "graphic_gadget.hpp"
#include "sprite_object.hpp"
#include <DxLib.h>


namespace modules {

    namespace factory {

        abstract__::Gadgets* GraphicGadget::createGadget(std::string filepath) {
            SpriteObject *sprite = new SpriteObject(filepath);
            return sprite;
        }

    }  // namespace factory

}  // namespace modules

ようやくcreateGadget(createProduct)が出てきましたね😓

ともあれ、これだけ作るのに大量に抽象クラスが追加になるのは、これがまだサンプルコードだからそう見えるだけです。

本当にこの恩恵を得ようと思ったら実際に数十、数百のオブジェクトを生成する必要があるその時にならないと分からないでしょう。

そして実際にこれを使うmain関数が以下です。

// ***************************************************
// 
// standby_state.hpp
//
// ***************************************************



#ifndef ERIS_DOMAIN_MODULES_PHASE_STANDBY_STATE_HPP_
#define ERIS_DOMAIN_MODULES_PHASE_STANDBY_STATE_HPP_

// includes.
#include "phase_state.hpp"
#include "../phase_context.hpp"
#include <vector>
#include "src/domain/modules/factory/sprite_object.hpp"


namespace modules {

    namespace phase {

        class StandbyState final : public interface__::IPhaseState {

        private:
            std::vector<factory::abstract__::Gadgets*> sprite_;

        public:
            StandbyState();
            void doExecute(interface__::IPhaseContext* obj);
                
        };

    }  // namespace phase

}  // namespace modules

#endif // !ERIS_DOMAIN_MODULES_PHASE_STANDBY_STATE_HPP_
// ***************************************************
// 
// standby_state.cpp
//
// ***************************************************



// includes.
#include <DxLib.h>
#include "standby_state.hpp"
#include "creditlogo_state.hpp"
#include "src/domain/inputkey/c16key.hpp"
#include "src/domain/modules/factory/gadgets.hpp"
#include "src/domain/modules/factory/sprite_object.hpp"
#include "src/domain/modules/factory/gadget_diecast.hpp"
#include "src/domain/modules/factory/graphic_gadget.hpp"


using namespace inputkey;

namespace modules {

    namespace phase {

        StandbyState::StandbyState() {
            using namespace factory;
            abstract__::GadgetDiecast *diecast = new GraphicGadget();
            abstract__::Gadgets *gadget = diecast->create("assets/graphic_material/sprites_tile/rockman/r2t.png");
            sprite_.push_back(gadget);
        }


        void StandbyState::doExecute(interface__::IPhaseContext* obj) {
            sprite_[0]->Use();
            if (1 == C16Key::getInstance()->getC16PressButtons(GPAD_BUTTON::START_KEY)) {
                MessageBox(GetMainWindowHandle(), "executed successfully of Eris opening demo process.", "State info", MB_OK);
                obj->recordLog("Process-Log", "executed successfully of Eris opening demo process.");
                obj->setState(new CreditLogoState());
            }
        }

    }  // namespace phase

}  // namespace modules

ソースは前回のStateパターンが混ざってしまっていますが。。。

まずGraphicGadgetのインスタンスを作成して、このインスタンスからGadget(Product)を直接生成するように指示を出します。

これで、gadgetにProductのアドレスが返ってきます。

これにより実際に具象クラス内でGadgetsインスタンスを作成する事なく、グラフィックデータなどをメモリロードする事ができます。

それにより、例えばグラフィックデータが複数のタイルから成る配列グラフィックデータになった場合は、メンバ変数をvectorなどに変換すれば良い。

例えば、画像データが音声データになった場合は、Productから別のSoundProductなどクラスを作成し、差し替えれば良い。

はたまた画像データをWebぺージなどから取得したい場合でもFactoryクラスのcreateProductメソッドを変更するだけで良い。


こういった形で、修正の際にプログラム全体が見直されずに済み、それだけ安定性が確保されている事が分かります。

そもそも仕様が途中で変わるのがダメやろ・・・とお思いのあなた。

世の中、最初から決まった人生を歩む人がいますか?

よく考えて下さい。

最初から100%完成されたゲームプログラムは、ありませんから😇

コメント

タイトルとURLをコピーしました