The Artima Developer Community
Sponsored Link

Agile Buzz Forum
Hard to be re-usable?

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Keith Ray

Posts: 658
Nickname: keithray
Registered: May, 2003

Keith Ray is multi-platform software developer and Team Leader
Hard to be re-usable? Posted: Nov 2, 2004 10:09 AM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by Keith Ray.
Original Post: Hard to be re-usable?
Feed Title: MemoRanda
Feed URL: http://homepage.mac.com/1/homepage404ErrorPage.html
Feed Description: Keith Ray's notes to be remembered on agile software development, project management, oo programming, and other topics.
Latest Agile Buzz Posts
Latest Agile Buzz Posts by Keith Ray
Latest Posts From MemoRanda

Advertisement

There is a meme going around that it is "an order of magnitude more difficult" to write a class that is "re-usable". That didn't seem right to me, so I decided to list the rules one could follow to write re-usable classes. Once I got up to about nine or ten rules, I decided that it might not be as simple as I thought. Still, compared to all the rules one must know to write safe C++ code, these rules are not that hard.

By the way, writing code test-first tends to make code much more re-usable, because the code is "used" at least twice - one or more times in the tests, and one or more times in the production code. Note: the previous statement is an understatement. If you as the 're-user' have access to the source code, and can refactor code as needed, then you don't have to follow all these rules initially. For example Rule 6 below doesn't need to be followed unless you determine that you need to do this to make re-use work, then you can refactor to make it work.

One obstacle to re-use is class dependencies and package dependencies. Once upon a time, I wanted to use a particular Java class, but it used so many other classes in the same package, that I was forced to add the entire package to my project. But that package used classes in a bunch of other packages. In order to use that single class, I would have had to include at least six packages and a hundred classes in my project. So...

Re-use Rule 1: Write your class to avoiding depending on other classes (etc). Obviously that can't always be done. Rule 1a: Try to use only the "basic" classes that everyone uses. More rules to come.

Problems that contribute to being forced to grab a lot of classes just to use one class are "too-big" classes and "too-big" packages.

Re-use Rule 2: a class should have one responsibility, delegating other responsibilities to other classes. Ditto for packages. Symptoms of having too many responsibilities are groups of methods that each only deal with isolated member variables, and having lots of private (or protected) methods.

Re-use Rule 3: a class should not depend on other concrete classes [except the most basic types like String]. By using interfaces or abstract classes as the "type" of member variables / return values / arguments to methods and constructors, we enable easier re-use (and testing): instead of the passing in the concrete types that were expected, we can pass in new concrete types useful in the new "re-use" context. Power of polymorphism and all that. Dynamic languages like Python, Smalltalk, Ruby and so on, which don't declare the types of variables, don't have this problem.

Re-use Rule 4: To implement rule 3 and to avoid circular package-dependencies (and to allow re-use without dragging in lots of packages), the interface, the class using that interface, and the concrete classes that implement that interface, probably need to reside in three different (and fairly small) packages. Package design regarding interfaces and concrete classes isn't discussed in many places, but it is discussed in Robert Martin's book "Agile Software Development".

Re-use Rule 4: to further allow substitution of concrete types, avoid using "new ConcreteClass" within your class -- particularly the constructors. If you do need to create objects of particular types (instead of having those objects passed into methods and constructors), put the "new" call into a "creation method". This allows the re-user of that class to override that method to instantiate a different kind of object. This is useful for creating Mock objects in unit-tests.

Re-use Rule 5: the user of you class may want to subclass-and-override. Make that possible by avoiding static, final, or non-virtual methods, and private methods. Private methods either need to be declared protected, or should be moved to another class and made public, with the original class using an instance of that other class.

Re-use Rule 6: You can make member variables protected, or make member variables private. If you make them private, you should implement protected (or, if absolutely necessary, public) accessor methods to get/set the member variables. A subclass can override those accessor methods to extend or modify behavior associated with getting/setting, including returning different objects. If you go this route, then no method or constructor should ever access those private variables directly except the accessor methods themselves.

Re-use Rule 7: In event that re-use of your class would involve overriding ALL of the methods, and ignoring ALL of the member variables, declare an interface and make your class implement that interface. Now instead of overriding every method in your class, the re-user can make their own implementation of your interface.

C++ makes following some of these rules difficult. For example, in a constructor, method calls to virtual functions act like the method is not declared virtual. In class "Parent", calls to virtual accessor methods to set member variables will not invoke overriding accessor methods implemented in class "Child." In this case, you would need to do work of setting member variables via virtual accessor methods in an "Initialize" method, which must NOT be called from the constructor. For example: "Parent* obj = new Child; obj->Initialize();". Don't forget to declare destructors virtual. C++ allows declaring argument types "by value" as well as "reference" and "pointer". "By value" doesn't allow polymorphism because it creates a new object of the declared type on the stack, copying a subset of the members from the object passed into that method call.

Following all of these rules ALL of the time may create ugly code. Make classes re-usable "as needed". Develop test-first so that you can later refactor classes for re-use at any time, protected from unintended bugs by suites of unit tests. There is a concept of "O.O. Normal Form" in Michael Feather's book "Working with Legacy Code" for class hierarchies summed up by the rule: "No child class's methods override any parent class's concrete methods." Any time you would be tempted to do that, you instead restructure the inheritance hierarchy, adding abstract classes or interfaces so that you only override unimplemented abstract/interface methods ("pure virtual" methods in C++.)

Here's an example of following these rules:


class Region   // the "not very re-usable" version of this class
{
public:
    Region();
    Region( Rect shape ); // "by value" argument  
    ~Region();  // not virtual, making subclassing dangerous
    void Clear(); // not virtual, can't be overridden.
    void Union( Rect );
    void Intersect( Rect );
    void GetBox() const;
    Bitmap ConvertToBitmap();
private:
    Bitmap myBitmap;
};

// Now the more re-usable version...

class RegionInterface
{
public:
    virtual ~RegionInterface();
    virtual void Clear();
    virtual void Union( const RectInterface& );
    virtual void Intersect( const RectInterface& );
    virtual void GetBox() const;
    virtual BitmapPtr ConvertToBitmap();
};

//BitmapPtr is a smart pointer declared something like this:
//typedef std::auto_ptr < BitmapInterface > BitmapPtr;

class Region : public RegionInterface
{
public:
    Region(); 
    virtual ~Region(); // "virtual" necessary for "delete (RegionSubclass*) obj" to work right.
    virtual void Initialize( const RectInterface & ); // because we can't call virtuals in the ctor
    virtual void Clear();
    virtual void Union( const RectInterface& );
    virtual void Intersect( const RectInterface& );
    virtual void GetBox() const;
    virtual BitmapPtr ConvertToBitmap();
protected:
    BitmapPtr GetBitmap() const;
    void SetBitmap( BitmapPtr );
private:
    BitmapPtr myBitmap;
};

Note that doing

    Region* reg = new Region;
    reg->Initialize( aRect );

more than once in the code-base is repeated code [a bad thing], so you would want to put those two lines into a creation-function (probably a static method in class Region). I've also left out copy constructors and assignment operators.

Read: Hard to be re-usable?

Topic: Canada swing Previous Topic   Next Topic Topic: Aggregating in hotels

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use