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.
Are there cases in OOP where some or all of the SOLID principles do not lend themselves to clean code?
In general, no. History has shown that the SOLID principles all largely contribute to increased decoupling, which in turn has been shown to increase flexibility in code and thus your ability to be accommodating of change as well as making the code easier to reason about, test, reuse... in short, make your code cleaner.
Now, there can be cases where the SOLID principles collide with DRY (don't repeat yourself), KISS (keep it simple stupid) or other principles of good OO design. And of course, they can collide with the reality of requirements, the limitations of humans, the limitations of our programming languages, other obstacles.
In short, SOLID principles will always lend themselves to clean code, but in some scenarios they'll lend themselves less than conflicting alternatives. They're always good, but sometimes other things are more good.
Best Answer
It is a bit difficult to find equivalents but I can try: