- Concrete classes considered harmful - 11 Updates
- Elementary question on references. - 5 Updates
- template variables - 3 Updates
- Elementary question on references. - 1 Update
legalize+jeeves@mail.xmission.com (Richard): Jul 13 05:08PM [Please do not mail me a copy of your followup] OK, this is a long post that covers ideas from object-oriented design and test-driven development. Ian Collins <ian-news@hotmail.com> spake the secret code >> Dependencies on concrete classes are considered harmful and should be >> replaced with dependencies on abstractions. >By whom? Well, I put Mr. Flibble in my KILL file when he couldn't control himself in responding to religious flamewar threads, so I didn't see the original post. I'm also unlikely to respond to anything else he says on this thread, because he's staying in my KILL file. However, it seems in this quoted statement Mr. Flibble is conflating (confusing?) two different pieces of advice. First, there is the dependency inversion principle (DIP) and there is the technique of dependency injection. "In object-oriented programming, the dependency inversion principle refers to a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are inverted (i.e. reversed), thus rendering high-level modules independent of the low-level module implementation details. The principle states: A. High-level modules should not depend on low-level modules. Both should depend on abstractions. B. Abstractions should not depend on details. Details should depend on abstractions. The principle inverts the way some people may think about object-oriented design, dictating that both high- and low-level objects must depend on the same abstraction." <https://en.wikipedia.org/wiki/Dependency_inversion_principle> This is the 'D' in the mnemonic 'SOLID' object oriented design principles. (This is related to, but not identical to, the idea of dependency injection in order to construct an object that depends on another object, but we will get to that in the 2nd point below.) <https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)> I have always interpreted this principle to be talking about "objects" as compared to "value types". I'm using these terms in the same sense as they are introduced in "Growing Object-Oriented Software, Guided by Tests" by Freeman and Pryce. <http://amzn.to/1Sjskpm>: "Values and Objects When designing a system, it's important to distinguish between _values_ that model unchanging quantities or measurements, and _objects_ that have an identity, might change state over time, and model _computational processes_." In a C++ program, value types are things like std::string and containers like std::vector and so-on. The "objects" are things whose identity matters or model a computational process. Suppose we have a class representing a person in a payroll application. This is more appropriately considered an object and not a value type. In that same payroll application there may be a class called Money that is used to represent payments between the employer and the employee. This is more appropriately considered a value type. In C++ value types are often called "concrete types", because they have no virtual methods and don't derive from an interface. They aren't intended to be part of a runtime polymorphic class hierarchy. The abstraction here is the base polymorphic interface that all derived members of the hierarchy implement in order to satisfy the Listkov Substitution Principle (LSP), the 'L' in 'SOLID'. However, C++ also supports static polymorphism through templates. In these cases, it is often intentional that there are no virtual methods. The template mechanisms do not preclude the use of virtual methods as well for a combination of static and dynamic polymorphism, but this is a fairly advanced template design pattern. Here the polymorphism is generally obtained through template parameters and not necessarily through inheritance. The template arguments are assumed to be models of a Concept <http://en.cppreference.com/w/cpp/concept>, such as a Forward Iterator <http://en.cppreference.com/w/cpp/concept/ForwardIterator>. In both cases, good design following DIP says that the details should depend on abstractions. An object collaborates with other objects through an interface so that both can vary independently without impacting the other. In C++, a web of collaborating objects following DIP gets the benefit of the "compiler firewall" because the changes to the implementations of collaborating interfaces don't cause their collaborators to be recompiled because they literally only depend on the abstraction of the interface. You put the interface in one header file and the implementation's declaration in another. In the static polymorphism case, reusing the standard algorithms doesn't require me to change my containers so long as they and their associated iterators model the appropriate concepts used by the iterator arguments to the algorithms. Similarly, I can write my own algorithms that operate seamlessly on the standard containers without those containers having been constructed with knowledge of my algorithms. The key mechanism that makes this work is the iterators which decouple the containers from the algorithms and vice-versa. The iterators are the abstraction upon which both containers and algorithms depend in order to cooperate with each other. >> member objects for example but rather should use a factory passed into >> its constructor. >Why? This is a very Java-esque way of looking at things, but the same technique can be applied in C++. It only makes sense for "objects" and not for "value types". There is no reason to introduce a factory of std::string, for instance, even if I create a value type that creates std::string dynamically. (Usually a value type will use composition instead of the heap, but a std::string is itself an example of a value type that uses the heap for storage.) So why introduce a factory for some type T instead of using 'new T'? Let's look at it from the perspective of unit testing. When I'm unit testing, I want to mock out the collaborators of the system under test (SUT). With dynamic polymorphism where the SUT (of type S) dynamically creates instances of a object collaborator (of type T, implementing some abstract interface I) dynamically, how can I sense the interaction with the dynamically created collaborator? If the concrete type T is used directly by S with 'new T', then I don't have a nice mechanism directly in the language for substituting a mock of type I under the circumstances of my test. S is really collaborating with I, the abstraction implemented by T, but you can't new up an interface, you can only new up an implementation of the interface, so it ends up being directly coupled to T. Anytime the implementation of I changes, T changes and forces S to be recompiled even though the underlying collaboration -- the interface abstraction I -- didn't change. This is because we aren't following the DIP and we have details depending on details. However, if S is constructed by taking a factory that creates instances of type T, then under test circumstances I can provide a factory that provides mock instances of I and in production circumstances I can provide a factory that creates instances of T that implement the abstraction I. With static polymorphism, there isn't any need for a factory because the type implementing the abstraction is directly supplied by the code instantiating the template. The expression 'new T' for some template parameter T in a template function or class isn't directly coupling the code to some specific implementation of T. In our test code we simply need to make sure that the expression 'new T' makes sense for our supplied mock type. Existing mock libraries let us write sensible expectations for template arguments, even when new instances are created dynamically, although it can get a bit tedious. There is probably some work that could be done in existing mock libraries to make this easier. So following the DIP implies that direct coupling of collaborating object types leads you to want to introduce factory interfaces so that instead of directly coupling the two implementations of objects you can couple them through abstractions instead. In a language like Java, that only supports dynamic polymorphism, that means that you don't allow 'new' when applied to object types. It is still allowed for value types. In a language like C++ that fully supports both static and dynamic polymorphism, you use the appopriate mechanism at the appropriate time. Virtual functions and dynamic polymorphism are great for modeling abstractions and collaborating interfaces. If you aren't dynamically creating collaborators, simply use dependency injection for your constructors and create the objects by passing them their collaborators. This technique is often used to follow the DIP, but the principle and the technique are different. <https://en.wikipedia.org/wiki/Dependency_injection> There is no need to use dependency injection for value types. I don't need to sense how a string is used inside an object I'm unit testing, I simply verify the object from the outside. For an instance of an object type, I might ask it to do something and verify it from externally visible properties of the object when I'm testing something state-oriented. (If the state is only internally visible and doesn't manifest itself in any way external to the object, then it doesn't make sense to try and write a test for that.) If I'm testing an object that models a computational process then I'm most likely interested in how this object interacted with its collaborators. I can sense that interaction through the use of mock collaborators. When I'm testing a value type object, it doesn't have any collaborators by definition. Everything I can do to a value type can be observed from the outside. Imagine yourself in the (unenviable) job of writing a set of unit tests to verify the behavior of std::string. You can cover every member function of std::string without knowing *anything* about how the string is internally represented. All the required semantics can be verified from its set of public member functions. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
legalize+jeeves@mail.xmission.com (Richard): Jul 13 05:10PM [Please do not mail me a copy of your followup] "Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com> spake the secret code >Which is, that practically nobody uses the above [SOLID] principle[s]. ;-) I disagree, the SOLID principles are embraced throughout the standard library. They just don't do it the 'Java way'. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
legalize+jeeves@mail.xmission.com (Richard): Jul 13 05:28PM [Please do not mail me a copy of your followup] Ian Collins <ian-news@hotmail.com> spake the secret code >Given the three options of abstract, concrete without non-trivial inline >member functions or Pimpl classes all are equally simple to mock. I'm guessing you're using a lot of link-time mocking? -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
legalize+jeeves@mail.xmission.com (Richard): Jul 13 05:46PM [Please do not mail me a copy of your followup] Ian Collins <ian-news@hotmail.com> spake the secret code >I agree. No one who uses TDD writes brain-dead isolated tests. I'm not sure what it means for a test to be "brain-dead isolated". However, there are plenty of times when I've started with tests and gotten to a good design without designing up-front any interfaces or abstractions explicitly. Did I have an idea of where I wanted to go in my head? Sure, but I didn't write it down as a "design document". -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
Mr Flibble <flibble@i42.co.uk>: Jul 13 07:59PM +0100 On 13/07/2015 18:08, Richard wrote: > [Please do not mail me a copy of your followup] > OK, this is a long post that covers ideas from object-oriented design > and test-driven development. [snip] I can't remember the last time I read so much that said so little. /Flibble |
Mr Flibble <flibble@i42.co.uk>: Jul 13 08:06PM +0100 On 13/07/2015 18:08, Richard wrote: > (confusing?) two different pieces of advice. First, there is the > dependency inversion principle (DIP) and there is the technique of > dependency injection. I am not confused at all: by passing a factory to a constructor you are effectively using *both* dependency injection AND dependency inversion. [snip] /Flibble |
Ian Collins <ian-news@hotmail.com>: Jul 14 08:47AM +1200 Richard wrote: >> Given the three options of abstract, concrete without non-trivial inline >> member functions or Pimpl classes all are equally simple to mock. > I'm guessing you're using a lot of link-time mocking? Yes. A lot of my work is low level system code and down there I'll be mocking mainly C libraries. -- Ian Collins |
Ian Collins <ian-news@hotmail.com>: Jul 14 08:52AM +1200 Richard wrote: > gotten to a good design without designing up-front any interfaces or > abstractions explicitly. Did I have an idea of where I wanted to go > in my head? Sure, but I didn't write it down as a "design document". I've been down the same path many times, especially what doing XP for one.. In bigger projects some minimal up-front interface design is necessary. -- Ian Collins |
legalize+jeeves@mail.xmission.com (Richard): Jul 13 10:01PM [Please do not mail me a copy of your followup] Ian Collins <ian-news@hotmail.com> spake the secret code >I've been down the same path many times, especially what doing XP for >one.. In bigger projects some minimal up-front interface design is >necessary. I agree. The larger the system you're designing, the more up-front work it requires. However I would say that most of the time when I was required to produce "design documents" it was largely busy work that could have been avoided by simply pair programming on the work. YMMV, of course. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
legalize+jeeves@mail.xmission.com (Richard): Jul 13 10:02PM [Please do not mail me a copy of your followup] Ian Collins <ian-news@hotmail.com> spake the secret code >> I'm guessing you're using a lot of link-time mocking? >Yes. A lot of my work is low level system code and down there I'll be >mocking mainly C libraries. OK, given that confirmation, have you looked at cgreen? It's a unit test framework built specifically around link-time mocking and the use of C. All my TDD work has been in C++ where I could use static or dynamic polymorphism for mocking out collaborators, so I haven't used link-time mocking. However, I can see it's usefulness. -- "The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline> The Computer Graphics Museum <http://computergraphicsmuseum.org> The Terminals Wiki <http://terminals.classiccmp.org> Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com> |
Ian Collins <ian-news@hotmail.com>: Jul 14 10:35AM +1200 Richard wrote: > OK, given that confirmation, have you looked at cgreen? It's a unit > test framework built specifically around link-time mocking and the use > of C. No, I haven't. I use my own mocking framework that is unit test framework neutral (it's been used with CppUnut, CxxTest and Google Test). I prefer to keep the two coupled so I can use my framework with whatever unit test setup a client uses. > All my TDD work has been in C++ where I could use static or > dynamic polymorphism for mocking out collaborators, so I haven't used > link-time mocking. However, I can see it's usefulness. It's pretty much the only option when you are working with system (or object only third party) libraries. My approach has been to apply the same techniques to pure C++ code and mixed C and C++. This leads to an unconventional approach to pure C++ code where it is the individual class member functions that are mocked, rather than class instances. While this may appear odd, I've found most test cases revolve around checking passed parameter values and responses to returned values. In these cases, being able to use the code generated by the framework rather than having to write mock objects saves a lot of work. -- Ian Collins |
"Lőrinczy Zsigmond" <zsiga@nospam.for.me>: Jul 13 08:41AM +0200 > int index = 5; > int& ref = vec[index]; > Is ref an alias for vec[5] or for vec[index]? Note: references aren't pointers, say the C++-fanboys. Nonetheless, they are pointers, immutable pointers with special syntax. So with assignments like this you can very easily get a dangling pointer: if you add/or delete elements to 'vec', address of vec[5] might be changed (cf: realloc), so 'ref' will point to an invalid address. |
"Öö Tiib" <ootiib@hot.ee>: Jul 13 04:20AM -0700 On Monday, 13 July 2015 09:43:12 UTC+3, Lőrinczy Zsigmond wrote: > > Is ref an alias for vec[5] or for vec[index]? > Note: references aren't pointers, say the C++-fanboys. > Nonetheless, they are pointers, immutable pointers with special syntax. That feels oversimplification since there are some other important differences besides immutability ... worth to note like ... no null references, no references of references, no arrays of references, no pointers to references and reference to temporary can extend life-time of that temporary. Such additional constraints can make references safer to use (for programmer) and simpler to optimize that usage (for compiler) than pointers. |
Bo Persson <bop@gmb.dk>: Jul 13 02:45PM +0200 On 2015-07-13 08:41, Lőrinczy Zsigmond wrote: >> Is ref an alias for vec[5] or for vec[index]? > Note: references aren't pointers, say the C++-fanboys. > Nonetheless, they are pointers, immutable pointers with special syntax. The similarity between a pointer and a reference is that they are both most often implemented by storing the address of an object. That doesn't mean that they are the same in any other way. You wouldn't say that all other types are the same, because they all store bits. Would you? Bo Persson |
"Lőrinczy Zsigmond" <nospam@for.me>: Jul 13 08:44PM +0200 On 2015.07.13. 18:40, Stefan Ram wrote: > Then, a variable like »i« in > { int i = 0; ... } > also is an »immutable pointer with special syntax«. Sure. Provided it contains an address and its value cannot be changed. |
"Öö Tiib" <ootiib@hot.ee>: Jul 13 02:03PM -0700 On Monday, 13 July 2015 21:44:37 UTC+3, Lőrinczy Zsigmond wrote: > > { int i = 0; ... } > > also is an »immutable pointer with special syntax«. > Sure. Provided it contains an address and its value cannot be changed. Reference is not said to be object that contains an address of object (or function). Reference is not object at all unlike pointer. It is said to be bound to refer to some object (or function) at initialization but standard does not say how. It is not empty propaganda of "C++ fanboys". It may be address, but when we look into code generated by compilers then reference to local, automatic storage variable is usually there as offset to stack (like that automatic storage variable itself). |
Doug Mika <dougmmika@gmail.com>: Jul 13 11:39AM -0700 If a template variable type can be deduced from the variables provided in the constructor of a template class, then is it needed? I thought it wasn't, but when in the following program (below) I replace the line: map<int, string, function<bool(const int&, const int&)>> mapIntToString1(ReverseSort<int>); with: map<int, string> mapIntToString1(ReverseSort<int>); the code no longer compiles? Why isn't the compiler able to deduce from the submitted function ReverseSort<int> that the template variable type is bool(*)(const int&, const int&)? Is the compiler only smart enough to deduce this for template functions? #include <iostream> #include <string> #include <map> #include <functional> using namespace std; template<typename KeyType> bool ReverseSort(const KeyType& key1, const KeyType& key2){ return (key1 > key2); } int main(int argc, char** argv) { //The Program map<int, string, function<bool(const int&, const int&)>> mapIntToString1(ReverseSort<int>); mapIntToString(ReverseSort<int>) mapIntToString1[1] = "one"; mapIntToString1[2] = "two"; mapIntToString1[3] = "three"; mapIntToString1[4] = "four"; for (auto m : mapIntToString1){ cout << m.first << "," << m.second << endl; } } |
Victor Bazarov <v.bazarov@comcast.invalid>: Jul 13 03:20PM -0400 On 7/13/2015 2:39 PM, Doug Mika wrote: > If a template variable type can be deduced from the variables > provided in the constructor of a template class, then is it needed? It can't. On what basis do you assume that it can? You ask a question that is not possible to answer because it is based on a wrong premise. > I thought it wasn't, but when in the following program (below) I replace the line: > map<int, string> > mapIntToString1(ReverseSort<int>); > the code no longer compiles? Why isn't the compiler able to deduce from the submitted function ReverseSort<int> that the template variable type is bool(*)(const int&, const int&)? It's not required to, by the language Standard. And the requirement is absent because it would be much more difficult to define other rules if it existed. > Is the compiler only smart enough to deduce this for template functions? Yes, the compiler is obviously dumber than you. The rules say that to define an object of some type you need to provide the type. Deduction of template arguments is only allowed by the Standard in some contexts, and the constructor arguments for direct initialization are not one of those contexts. A constructor is not a regular function. Find a copy of a good book that explains it and study. A decent book enumerates the rules and shows what rules exist. A great book also explains why such rules exist. The former is often enough, the latter is sometimes too difficult to comprehend. > [..] V -- I do not respond to top-posted replies, please don't ask |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Jul 13 10:11PM +0200 On 13-Jul-15 8:39 PM, Doug Mika wrote: > submitted function ReverseSort<int> that the template variable type is > bool(*)(const int&, const int&)? Is the compiler only smart enough to deduce > this for template functions? The language was not designed to support this, but the need has become more and more manifest, with all kinds of "make" functions being defined. There is some discussion at SO, at <url: http://stackoverflow.com/questions/29677505/why-cant-constructors-deduce-template-arguments> There is a current proposal to support this, N4471, referenced from the SO discussion, <url: http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4471.html> and it lists some of the difficulties, in particular that * The constructor argument type can be indirectly defined in terms of a template parameter, which then is difficult to deduce. I'm not sure I agree with the comment in the "inject class name" example though. This looks like still a pretty rough draft that needs to be ironed out. I can't see that it will make C++17. But never say never! :) Cheers & hth., - Alf -- Using Thunderbird as Usenet client, Eternal September as NNTP server. |
ram@zedat.fu-berlin.de (Stefan Ram): Jul 13 04:40PM >references ... >are pointers, immutable pointers with special syntax. Then, a variable like »i« in { int i = 0; ... } also is an »immutable pointer with special syntax«. |
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment