DRY Principle – Good Practices for DRY Principle in Design Patterns

design-patternsobject-orientedPHP

I am trying to follow the DRY principle in my programming as hard as I can. Recently I have been learning design patterns in OOP and I have ended up repeating myself quite a bunch.

I have created a Repository pattern along with a Factory and Gateway patterns to handle my persistence. I'm using a database in my application but that shouldn't matter since I should be able to swap out the Gateway and switch to another kind of persistence if I wished.

The problem I've ended up creating for myself is that I create the same objects for the number of tables I have. For example these will be the objects I need to handle a table comments.

class Comment extends Model {

    protected $id;
    protected $author;
    protected $text;
    protected $date;
}

class CommentFactory implements iFactory {

    public function createFrom(array $data) {
        return new Comment($data);
    }
}

class CommentGateway implements iGateway {

    protected $db;

    public function __construct(\Database $db) {
        $this->db = $db;
    }

    public function persist($data) {

        if(isset($data['id'])) {
            $sql = 'UPDATE comments SET author = ?, text = ?, date = ? WHERE id = ?';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date'], $data['id']);
        } else {
            $sql = 'INSERT INTO comments (author, text, date) VALUES (?, ?, ?)';
            $this->db->prepare($sql)->execute($data['author'], $data['text'], $data['date']);
        }
    }

    public function retrieve($id) {

        $sql = 'SELECT * FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }

    public function delete($id) {

        $sql = 'DELETE FROM comments WHERE id = ?';
        return $this->db->prepare($sql)->execute($id)->fetch();
    }
}

class CommentRepository {

    protected $gateway;
    protected $factory;

    public function __construct(iFactory $f, iGateway $g) {
        $this->gateway = $g;
        $this->factory = $f;
    }

    public function get($id) {

        $data = $this->gateway->retrieve($id);
        return $this->factory->createFrom($data);
    }

    public function add(Comment $comment) {

        $data = $comment->toArray();
        return $this->gateway->persist($data);
    }
}

Then my controller looks like

class Comment {

    public function view($id) {

        $gateway = new CommentGateway(Database::connection());
        $factory = new CommentFactory();
        $repo = new CommentRepository($factory, $gateway);

        return Response::view('comment/view', $repo->get($id));
    }
}

So I thought I was using design patterns correctly and keeping good practices, but the problem with this thing is that when I add a new table, I have to create the same classes just with other names. This raises suspicion in me that I may be doing something wrong.

I thought of a solution where instead of interfaces I had abstract classes which using the class name figure out the table they need to manipulate but that doesn't seem like the right thing to do, what if I decide to switch to a file storage or memcache where there are no tables.

Am I approaching this correctly, or is there a different perspective I should be looking at?

Best Answer

The problem you are addressing is quite fundamental.

I have experienced the same problem when I worked for a company that made a large J2EE application that consisted of several hundred web pages and over a million and a half lines of Java code. This code used ORM (JPA) for persistence.

This problem get worse when you use 3rd party technologies in every layer of the architecture and the technologies all require their own data representation.

Your problem can not be solved at the level of the programming language you are using. Using patterns is good but as you see it cause repetition of code (ore more accurately put: repetition of designs).

The way I see it there are only 3 possible solutions. In practice these solutions come down to the same.

Solution 1: Use some other persistence framework that allows you to state only what must be persisted. There is probably such a framework around. The problem with this approach is that it is rather naive because not all you patterns will be persistence related. You wil also want to use patterns for user interface code so you'd then need a GUI framework that can re-use the data representations of the persistence framework you choose. If you can not re-use them you'll need to write boiler plate code to bridge the data representations of the GUI framework and the persistence framework.. and this is contrary to the DRY principle again.

Solution 2: Use another - more powerful - programming language that has constructs that allow you to express the repetitive design so you can re-use the design-code. This is probably not an option for you but suppose for a moment it is. Then again when you start creating a user interface on top of the persistence layer you'll want the language again to be powerful enough to support creating the GUI without having to write boiler plate code. It is unlikely that there is a language powerful enough to do what you want since most languages rely on 3rd party frameworks for GUI building that each require their own data representation to work.

Solution 3: Automate the repetition of code and design using some form of code generation. Your worry is about having to hand-code repetitions of patterns and designs since hand-coding repetitive code/design violate the DRY principle. Nowadays there are very powerful code generator frameworks out there. There are even "language workbenches" that allow you to quickly (half a day when you have no experience) create your own programming language and generate any code (PHP/Java/SQL - any thinkable text file) using that language. I have experience with XText but MetaEdit and MPS seem to be fine as well. I strongly advice you to check one of these language workbenches out. To me it was the most liberating experience in my professional life.

Using Xtext you can have your machine generate the repetitive code. Xtext even generates a syntax highlighting editor for you with code completion for your own language specification. From that point you simply take your gateway and factory class and turn them into code templates by punching holes in them. You feed them to your generator (which is called by a parser of your language that is also completely generated by Xtext) and the generator will fill in the holes in your templates. The result is generated code. From that point on you can take out any repetition of code anywhere (GUI code persistence code etc).