10

I'm trying to code in C++ (C++11) a very simple example according to the Clean Architecture concept described by Uncle Bob Martin here (picture below):

enter image description here

The idea is to read some text by a Controller and print it by a Presenter. I have done something but it doesn't look like it's following the clean flow and the DIP of the blog post.

Among other things, I think the flow is wrong as, for example, the IUseCaseInputPort needs to know about the IUseCaseOutputPort (the read function has the IUseCaseOutputPort as input parameter, creating thus another dependency...).

I would really appreciate if someone could give me some tips about the best way to implement this. Many thanks in advance.

#include <iostream>
#include <string>
#include <memory>

class IUseCaseOutputPort {
public:
    virtual void print(std::string message) = 0;
    virtual ~IUseCaseOutputPort() {};
};

// 2 Presenters
class HtmlPresenter: public IUseCaseOutputPort {
public:
    void print(std::string message) {
        std::cout << "<p>" << message << "</p>" << std::endl;
    }
};

class TextPresenter: public IUseCaseOutputPort {
public:
    void print(std::string message) {
        std::cout << message << std::endl;
    }
};

//
class IUseCaseInputPort {
public:
    virtual void read(std::shared_ptr<IUseCaseOutputPort> output) = 0;
    virtual ~IUseCaseInputPort(){};
};

// specific UseCaseInteractor
class UseCaseInteractorForInputFromStdIn: public IUseCaseInputPort {
public:
    void read(std::shared_ptr<IUseCaseOutputPort> output) {
        std::string message;
        std::cout << "Please input some text!";
        std::getline(std::cin, message);
        output->print(message);
    }
};

// Controller
class ControllerToDisplayHtml {
public:
    void displayInHtmlSomethingFromStdIn() {
        input = std::make_shared<UseCaseInteractorForInputFromStdIn>();
        std::shared_ptr<HtmlPresenter> output =
                std::make_shared<HtmlPresenter>();
        input->read(output);
    }
private:
    std::shared_ptr<IUseCaseInputPort> input;
};

int main() {
    ControllerToDisplayHtml c;
    c.displayInHtmlSomethingFromStdIn();
    return 0;
}

For those interested, a complement to my question as suggested by BЈовић. Very simple example. Just to show the flow of this model.

#include <iostream>
#include <string>
#include <memory>
#include <fstream>

class IUseCaseOutputPort {
public:
    virtual void print(std::string message) = 0;
    virtual ~IUseCaseOutputPort() {};
};

// 2 Presenters
class HtmlPresenter: public IUseCaseOutputPort {
public:
    void print(std::string message) {
        std::cout << "<p>" << message << "</p>" << std::endl;
    }
};

class TextPresenter: public IUseCaseOutputPort {
public:
    void print(std::string message) {
        std::cout << message << std::endl;
    }
};

//
class IUseCaseInputPort {
public:
    virtual std::string read() = 0;
    virtual ~IUseCaseInputPort(){};
};

// specific UseCaseInteractor for reading text from the stdin
class UseCaseInteractorForInputFromStdIn: public IUseCaseInputPort {
public:
    std::string read() {
        std::string message;
        std::cout << "Please input some text!" << std::endl;
        std::getline(std::cin, message);
        return message;
    }
};

// specific UseCaseInteractor for reading text from a dummy file
class UseCaseInteractorForInputFromDummyFile: public IUseCaseInputPort {
public:
    std::string read() {
        const std::string filename = "/proc/meminfo";
        std::string message = readFile(filename);
        return message;
    }
private:
    std::string readFile(const std::string filename) {
        std::string line;
        std::string lines;
        std::ifstream myfile(filename);
        if (myfile.is_open()) {
            while (myfile.good()) {
                getline(myfile, line);
                lines += line + '\n';
            }
            myfile.close();
        } else {
            lines = "Unable to open file";
        }
        return lines;
    }
};

// Controller
class ControllerForReading {
public:
    std::string readFromStdIn() {
        input = std::make_shared<UseCaseInteractorForInputFromStdIn>();
        std::string out = "This text was read from the stdin:\n";
        out += input->read();
        return out;
    }
    std::string readFromFile() {
        input = std::make_shared<UseCaseInteractorForInputFromDummyFile>();
        std::string out = "This text was read from the a file:\n";
        out += input->read();
        return out;
    }
private:
    std::shared_ptr<IUseCaseInputPort> input;
};

// main represents the outer shell
int main() {
    std::cout << "Main started!" << std::endl;

    ControllerForReading c;
    const std::string textFromStdin = c.readFromStdIn();
    const std::string textFromFile = c.readFromFile();

    auto output = std::make_shared<HtmlPresenter>();
    output->print(textFromStdin);
    output->print(textFromFile);

    auto output2 = std::make_shared<TextPresenter>();
    output2->print(textFromStdin);
    output2->print(textFromFile);

    std::cout << "Main ended!" << std::endl;
    return 0;
}
Jesse
  • 3,243
  • 1
  • 22
  • 29
RicLeal
  • 923
  • 9
  • 23
  • 3
    If your question is really about code-style (rather than about a bug), you should probably ask it at http://codereview.stackexchange.com. – Oliver Charlesworth May 29 '13 at 10:37
  • Thanks. It doesn't look neither like bug nor code-style... Just want to avoid the extra dependency I introduced. If I don't get any replies, will post it where you suggested. – RicLeal May 29 '13 at 10:46

1 Answers1

7

Among other things, I think the flow is wrong as, for example, the IUseCaseInputPort needs to know about the IUseCaseOutputPort (the read function has the IUseCaseOutputPort as input parameter, creating thus another dependency...).

Yes, this is indeed wrong. A method to get data should not know what is being done with it.

A fix is quite simple. Change IUseCaseInputPort::read method to return the result, instead of calling IUseCaseOutputPort's method :

//
class IUseCaseInputPort {
public:
    virtual std::string read() = 0;
    virtual ~IUseCaseInputPort(){};
};

// specific UseCaseInteractor
class UseCaseInteractorForInputFromStdIn: public IUseCaseInputPort {
public:
    std::string read() {
        std::string message;
        std::cout << "Please input some text!";
        std::getline(std::cin, message);
        return message;
    }
};

// Controller
class ControllerToDisplayHtml {
public:
    void displayInHtmlSomethingFromStdIn() {
        input = std::make_shared<UseCaseInteractorForInputFromStdIn>();
        std::shared_ptr<HtmlPresenter> output =
                std::make_shared<HtmlPresenter>();

        const std::string messageToOutput( input->read() );
        output->print(messageToOutput);
    }
private:
    std::shared_ptr<IUseCaseInputPort> input;
};

One more thing. You should not create input and output objects in the displayInHtmlSomethingFromStdIn() method. Instead you should use some kind of dependency injection. That means, you create these objects outside, and pass them through pointer or reference to the ControllerToDisplayHtml object.

BЈовић
  • 62,405
  • 41
  • 173
  • 273