junrrein / pdfslicer

A simple application to extract, merge, rotate and reorder pages of PDF documents
https://junrrein.github.io/pdfslicer/
GNU General Public License v3.0
142 stars 18 forks source link

SRP violation. #15

Open Thejuampi opened 5 years ago

Thejuampi commented 5 years ago

https://github.com/junrrein/pdfslicer/blob/8c74b34d44ef85cd206dbed5d9ad7dd715e36977/src/application/application.cpp#L74

AppWindow should be responsible only of displaying data. You could have an DocumentOpener that knows only how to open a document. This implies having an interface that could be tested.

DocumentOpener::open(File aDocumentFile)

In the same way, there could be a DocumentSaver with a save(aDocument)

junrrein commented 5 years ago

This issue is more complex than what can be seen at first hand.

In the first place, a Gtk::ApplicationWindow already has more than one responsibility (as the inheritance graph hints):

With that being said, let's focus on the particular issue you raised.

If you look at the implementation of AppWindow::OpenDocument you will see that only the first line is involved in opening the document:

https://github.com/junrrein/pdfslicer/blob/8c74b34d44ef85cd206dbed5d9ad7dd715e36977/src/ui/appwindow.cpp#L49

Everything else deals with displaying the newly loaded Document. So the method is badly named.

For now, I propose taking that first line out of the method, rename the method to setDocument, and have a Document be the parameter to the method (instead of a file). Edit: Done in 3aa1c888e65a78bc1a4d5787439a67d45708a858

I'll talk about the SRP violation in the next comment.

junrrein commented 5 years ago

The interface of Document is the following:

public:
    Document(const std::string& filePath);
    ~Document();

    void saveDocument(const Glib::RefPtr<Gio::File>& destinationFile);
    void removePage(int pageNumber);
    void removePages(const std::vector<unsigned int>& positions);
    void removePageRange(int first, int last);
    void rotatePagesRight(const std::vector<unsigned int>& pageNumbers);
    void rotatePagesLeft(const std::vector<unsigned int>& pageNumbers);
    void undoCommand() { m_commandManager.undo(); }
    void redoCommand() { m_commandManager.redo(); }

    bool canUndo() const { return m_commandManager.canUndo(); }
    bool canRedo() const { return m_commandManager.canRedo(); }
    const Glib::RefPtr<Gio::ListStore<Page>>& pages() const { return m_pages; }

    sigc::signal<void>& commandExecuted() { return m_commandManager.commandExecuted; }
    sigc::signal<void, std::vector<unsigned int>> pagesRotated;

We can separate the responsibilites into three parts:

If we can agree that these are the responsibilities, then the class should be separated into three:

I'm not sure about how to technically go about doing this. This is how I've thought about it:

What do you think?

junrrein commented 5 years ago

We now have separate DocumentSaver and PageRenderer classes.

I'm not liking the idea of a DocumentLoader class since it will be too tightly coupled with the private members of Document.