0

for example, I have some Fruits:

Fruit.h

#ifndef __Fruit__
#define __Fruit__
#include <string>
class Fruit{
public:
    virtual void hi(std::string username)=0;
};
#endif

Apple.h

#include "Fruit.h"
#include <stdio.h>
class Apple : public Fruit{
public:
    virtual void hi(std::string message){
        printf("Hi %s,I am apple\n",message.c_str());
    }
};

Orange.h

#include "Fruit.h"
#include <stdio.h>
class Orange : public Fruit{
public:
    virtual void hi(std::string message){
        printf("Hi %s,I am orange\n",message.c_str());
    }
};

I need to decide which to use according to input string:

#include "Apple.h"
#include "Orange.h"
int main(){
    std::string username="abc";
    std::string input="Orange";
    if(input=="Apple"){
        Apple().hi(username);
    }else if(input=="Orange"){
        Orange().hi(username);
    }
    return 0;
}

I know it doesn't obey open closed principle because adding a new Fruit needs to add a new if-else condition to map to the correct function. I heard registry pattern can make this case obey open closed principle, is it true? if so, how to implement registry pattern here?

ggrr
  • 7,737
  • 5
  • 31
  • 53
  • The key pattern to implement is the abstract factory that creates the objects. The registry pattern is then used to select the abstract factory. – Sebastian Redl Jun 23 '17 at 08:33
  • use polymorphism, otherwise apple and orange will be available only in the `if` – sop Jun 23 '17 at 08:36

3 Answers3

2

This can become quite verbose codewise so I use words instead. A definition of the registry pattern :

A registry is a global association from keys to objects, allowing the objects to be reached from anywhere. It involves two methods: one that takes a key and an object and add objects to the registry and one that takes a key and returns the object for the key

The thing here is that the registry doesn't know how to build the object, just how to retrieve it. This is a significant difference to the creational patterns.

Basically your fruit should can change to

#include <string>
class Fruit{
public:
    virtual std::string key() const = 0;
    virtual void hi(std::string username) = 0;
};

And now you introduce a registry

class FruitRegistry final {
public:
    bool register(Fruit* fruit);
    Fruit* locate(std::string key);

private: 
   std::map<string, Fruit*> registry;
};

The means of registering/retrieving fruits should be the same whatever the fruit. Here it can be done using a map string to fruit. You can also design the class fruit so it uses an accept method, which is quite useful when the input is complex (think initializing a fruit depending on its description).

When it is useful ? When it is used behind an interface to access a type of resources, like img.load("cats.jpg"). The format jpg, bmp, png are quite different and might need a separate engine for each, or one for jpg, the other for both bmp and png. But the user doesn't care about those details. Note that more and more images type can be added in the future without touching the image loading.

The issue is that to provide the nice img.load("cats.jpg") the registry mechanism underneath can be complex to design. You need to ask yourself :

  • Do the user care about apple and oranges (if no stop here, you don't need a registry)? Or does he just want to eat a fruit ?
  • How easy will it be to eat fruits the registry is implemented? (should be a single line)
  • How often or how long new types of fruits can be added? (should be forever)
  • How different are the fruits ? (should be completely, just that they can be eaten)
UmNyobe
  • 22,539
  • 9
  • 61
  • 90
1

Look on Factory Method pattern. It is what you need.

sop
  • 3,445
  • 8
  • 41
  • 84
Roman Kazmin
  • 931
  • 6
  • 18
1

I think I'd create a FruitRegistry class, register generators with it and use it to generate my fruits.

full example, using a static implementation of the registry (c++14):

#include <string>
#include <iostream>
#include <stdexcept>
#include <unordered_map>
#include <memory>

class Fruit{
public:
    virtual void hi(std::string username)=0;
};

// static implementation, but it needn't be if you want different registers
class FruitRegister
{
    using signature_type = std::unique_ptr<Fruit>();
    using function_type = std::function<signature_type>;

    struct Impl {
        template<class Function>
        void add(std::string name, Function&& f)
        {
            generators_.emplace(name, std::forward<Function>(f));
        }

        std::unique_ptr<Fruit> generate(std::string const& name) const
        {
            // NOTE: can throw if bad index
            auto& generator = generators_.at(name);
            return generator();
        }

    private:
        std::unordered_map<std::string, 
                           function_type> generators_;
    };

    static Impl& get_impl() {
        static Impl impl_ {};
        return impl_;
    }

public:
    template<class Function>
    void add(std::string name, Function&& f)
    {
        get_impl().add(name, std::forward<Function>(f));
    }

    std::unique_ptr<Fruit> generate(std::string const& name) const
    {
        return get_impl().generate(name);
    }
};


class Apple : public Fruit{
public:
    virtual void hi(std::string message){
        printf("Hi %s,I am apple\n",message.c_str());
    }
};

class Orange : public Fruit{
public:
    virtual void hi(std::string message){
        printf("Hi %s,I am orange\n",message.c_str());
    }
};

//
// register all variants here
//
const bool added = [] {
    auto r = FruitRegister();
    r.add("Apple", []() { return std::make_unique<Apple>(); });
    r.add("Orange", []() { return std::make_unique<Orange>(); });

return true;
}();

int main(){
    std::string username="abc";
    std::string input="Orange";

    auto fr = FruitRegister();
    auto thing = fr.generate(input);
    thing->hi(username);
    return 0;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142