You may start from a different point of view to apply "Single Responsibility Principle" here. What you have shown to us is (more or less) only the data model of your application. SRP here means: make sure your data model is responsible only for keeping data - no less, no more.
So when you are going to read your XML file, create a data model from it and write SQL, what you should not do is implement anything into your Table
class which is XML or SQL specific. Your want your data flow look like this:
[XML] -> ("Read XML") -> [Data model of DB definition] -> ("Write SQL") -> [SQL]
So the only place where XML specific code should be placed is a class named, for instance, Read_XML
. The only place for SQL specific code should be a class like Write_SQL
. Of course, maybe you are going to split those 2 tasks into more sub-tasks (and split your classes into multiple manager classes), but your "data model" should not take any responsibility from that layer. So don't add a createStatement
to any of your data model classes, since this gives your data model responsibility for the SQL.
I don't see any problem when you are describing that a Table is responsible for holding all it's parts, (name, columns, comments, constraints ...), that is the idea behind a data model. But you described "Table" is also responsible for the memory management of some of its parts. That's a C++ specific issue, which you would not face so easily in languages like Java or C#. The C++ way of getting rid of those responsibility is using smart pointers, delegating ownership to a different layer (for example, the boost library or to your own "smart" pointer layer). But beware, your cyclic dependencies may "irritate" some smart pointer implementations.
Something more about SOLID: here is nice article
http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game
explaining SOLID by a small example. Let's try to apply that to your case:
you will need not only classes Read_XML
and Write_SQL
, but also a third class which manages the interaction of those 2 classes. Lets call it a ConversionManager
.
Applying DI principle could mean here: ConversionManager should not create instances of Read_XML
and Write_SQL
by itself. Instead, those objects can be injected through the constructor. And the constructor should have a signature like this
ConversionManager(IDataModelReader reader, IDataModelWriter writer)
where IDataModelReader
is an interface from which Read_XML
inherits, and IDataModelWriter
the same for Write_SQL
. This makes a ConversionManager
open for extensions (you very easily provide different readers or writers) without having to change it - so we have an example for the Open/Closed principle. Think about it what you will have to change when you want to support another database vendor -ideally, you don't have to change anything in your datamodel, just provide another SQL-Writer instead.
No, the two concepts are different.
A better car analogy would be regarding the engine. Let us assume we have an interface for an engine. It might have properties such as number of cylinders and RPM. You might be able to invoke behavior such as opening and closing the throttle. However, this is just an interface: engine is not an implementation. The Car class has-a engine: when you construct a car, you install something that implements that engine interface. Many cars have different engines as options.
Now there is the implementation. Perhaps we define a SixCylinderEngine
class. It is-a engine because it implements that interface. It can be used anywhere the engine interface is used.
But wait! Some people want a supercharged version of the engine. This is not as simple as changing a variable in the existing engine, this is a major change. Different parts, a second drive belt, another major component (the supercharger), more horsepower, etc.
One way to accomplish this would be to subclass SixCylinderEngine
and add in the supercharger. That has merit, but can introduce complex interdependencies between the subclass and superclass. That would be another is-a relationship.
Instead, we define a SuperchargedSixCylinderEngine
class that implements Engine directly (is-a). In order to reuse code, it contains a SixCylinderEngine
internally and delegates certain functions (has-a).
From here, the next step is to have a SuperchargedEngine
which accepts any engine: 6, 8, 10 cylinder, anything you want.
Here is a code example of the difference.
Inheritence:
public int getHorsepower() {
return 1.4 * super.getHorsepower();
}
Composition:
private Engine engine;
public SuperchargedEngine(Engine e) {
engine = e;
}
public int getHorsepower() {
return 1.4 * engine.getHorsepower();
}
Best Answer
TDD isn't hugely popular in the ObjC world. Look at some of the official Apple SDK libraries and you'll find patterns that would make TDD advocates give up in disgust (there are a lot of singletons in the iOS SDK, and it's a pattern that works very well with ObjC). Unit tests in general aren't that popular, either, which explains why the official unit test suite is a relatively recent edition to Xcode and why it's still third-rate. The ObjC philosophy seems to be:
That'll undoubtedly rattle the TDD guys, but it's the approach I've noted in ObjC developers.
ObjC takes a more pragmatic approach to protocols than Java devs do to interfaces ("every class must have an interface and an implementation, just in case!"):
This is why you only tend to see protocols used in conjunction with delegates: they tend to be the only places that you'll create more than one class to implement the necessary functionality, especially as the delegate pattern is so pervasive. Other languages tend to require you to either subclass and override or implement an interface to provide additional functionality; ObjC tends to use delegates instead.
The fact that ObjC is dynamic removes a lot of the need for protocols/interfaces. Java, C++ and C# developers spend a lot of time trying to categorize and type everything as accurately as they can, resulting in multitudes of interfaces and classes. That simply isn't necessary in a dynamic language (and it's one of the reasons I'm so happy to have switched from C# to ObjC: I can spend my time solving problems instead of describing type hierarchies).
ObjC is dynamic and supports duck typing, so if an object implements a method "quack" you can call that method without explicitly conforming to the "Duck" protocol; you can even identify if the object implements the method before calling it thanks to ObjC's excellent metaprogramming capabilities.