Design Question: Inheritance or Accessors

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Ben Pope

    Design Question: Inheritance or Accessors

    Hi everybody,

    I know this is not usually the place for design questions, but I was wondering what some of you more experienced people would do.

    Say I have an application that has a C++ & STL core, with a user interface that could be command-line (on topic, I guess) or even .NET/QT/Whatever UI (obviously off-topic, here).

    One of the classes in the core stores some data, in a vector. I want to display that information to the user.

    Is it preferable to provide an accessor for the private data, e.g.:

    std::vector<T> GetData() const {
    return storage_;
    }

    With which the UI can obtain the data - which obviously loses some of my encapsulation, or would it be preferable to make the storage protected, and have the UI component inherit
    from the class (probably multiple inheritance required in this case)?

    I would prefer not to have any dependency of the UI within the core, so making the UI a friend would be difficult.

    Cheers!

    Ben
    --
    I'm not just a number. To many, I'm known as a String...
  • Dan Cernat

    #2
    Re: Design Question: Inheritance or Accessors


    "Ben Pope" <benpope81@_REM OVE_gmail.com> wrote in message
    news:1122234898 .3f87a0a1ac3b33 b90f608e8290e7d f8e@teranews...[color=blue]
    > Hi everybody,
    >
    > I know this is not usually the place for design questions, but I was
    > wondering what some of you more experienced people would do.
    >
    > Say I have an application that has a C++ & STL core, with a user interface
    > that could be command-line (on topic, I guess) or even .NET/QT/Whatever UI
    > (obviously off-topic, here).
    >
    > One of the classes in the core stores some data, in a vector. I want to
    > display that information to the user.
    >
    > Is it preferable to provide an accessor for the private data, e.g.:
    >
    > std::vector<T> GetData() const {
    > return storage_;
    > }
    >[/color]

    I would return const reference to data. This way one cannot change it.

    const std::vector<T>& GetData() const
    {
    return storage_;
    }

    Dan


    Comment

    • Cy Edmunds

      #3
      Re: Design Question: Inheritance or Accessors

      "Ben Pope" <benpope81@_REM OVE_gmail.com> wrote in message
      news:1122234898 .3f87a0a1ac3b33 b90f608e8290e7d f8e@teranews...[color=blue]
      > Hi everybody,
      >
      > I know this is not usually the place for design questions, but I was
      > wondering what some of you more experienced people would do.
      >
      > Say I have an application that has a C++ & STL core, with a user interface
      > that could be command-line (on topic, I guess) or even .NET/QT/Whatever UI
      > (obviously off-topic, here).
      >
      > One of the classes in the core stores some data, in a vector. I want to
      > display that information to the user.
      >
      > Is it preferable to provide an accessor for the private data, e.g.:
      >
      > std::vector<T> GetData() const {
      > return storage_;
      > }
      >
      > With which the UI can obtain the data - which obviously loses some of my
      > encapsulation, or would it be preferable to make the storage protected,
      > and have the UI component inherit from the class (probably multiple
      > inheritance required in this case)?
      >
      > I would prefer not to have any dependency of the UI within the core, so
      > making the UI a friend would be difficult.
      >
      > Cheers!
      >
      > Ben
      > --
      > I'm not just a number. To many, I'm known as a String...[/color]

      I would give the user some iterators:

      template <typename T>
      class YourClass {
      public:
      typedef std::vector<T> t_container;
      typedef t_container::co nst_iterator const_iterator;
      const_iterator begin() const {return storage_.begin( );}
      const_iterator end() const {return storage_.end(); }
      ....
      };

      A smart client should be able to write his code in such a way that nothing
      breaks if you change to a different container type.

      --
      Cy



      Comment

      • Ben Pope

        #4
        Re: Design Question: Inheritance or Accessors

        Dan Cernat wrote:[color=blue]
        > "Ben Pope" <benpope81@_REM OVE_gmail.com> wrote in message
        > news:1122234898 .3f87a0a1ac3b33 b90f608e8290e7d f8e@teranews...
        >[color=green]
        >>Is it preferable to provide an accessor for the private data, e.g.:
        >>
        >>std::vector<T > GetData() const {
        >> return storage_;
        >>}
        >>[/color]
        >
        >
        > I would return const reference to data. This way one cannot change it.
        >
        > const std::vector<T>& GetData() const
        > {
        > return storage_;
        > }[/color]

        You'd prefer const reference over copy by value?

        My original question was going to be over this particular issue, but I thought by-value was generally preferred, and then I thought of the inheritance thing, and wondered about
        that "more".

        Cheers,

        Ben
        --
        I'm not just a number. To many, I'm known as a String...

        Comment

        • Ben Pope

          #5
          Re: Design Question: Inheritance or Accessors

          Cy Edmunds wrote:[color=blue]
          > "Ben Pope" <benpope81@_REM OVE_gmail.com> wrote in message
          > news:1122234898 .3f87a0a1ac3b33 b90f608e8290e7d f8e@teranews...
          >[color=green]
          >>Is it preferable to provide an accessor for the private data, e.g.:
          >>
          >>std::vector<T > GetData() const {
          >> return storage_;
          >>}[/color]
          >
          > I would give the user some iterators:
          >
          > template <typename T>
          > class YourClass {
          > public:
          > typedef std::vector<T> t_container;
          > typedef t_container::co nst_iterator const_iterator;
          > const_iterator begin() const {return storage_.begin( );}
          > const_iterator end() const {return storage_.end(); }
          > ...
          > };
          >
          > A smart client should be able to write his code in such a way that nothing
          > breaks if you change to a different container type.[/color]

          Hmm, interesting. I had toyed briefly with that idea, but hadn't formulated any real ideas.

          I suppose the whole point is not to return the vector, giving access to the iterators, but to return the iterators themselves.

          Is it possible to return a "forward container"? I don't see that as some concrete interface or base class anywhere in the STL, I presume it's "just" a concept, not tangible?

          I suppose I could enforce a similar effect by doing:

          typedef t_container::fo rward_iterator forward_iterato r;

          And if I wanted to be able to do:
          *(YourClass.beg in() + 4);

          typedef t_container::ra ndom_access_ite rator random_access_i terator;

          This is very interesting.

          I must re-read some of the STL concepts with a different hat on (one that asks how I would give my class these concepts, rather then how I can use these concepts).

          Thank you.

          Ben
          --
          I'm not just a number. To many, I'm known as a String...

          Comment

          • Ben Pope

            #6
            Re: Design Question: Inheritance or Accessors

            Ben Pope wrote:[color=blue]
            > Cy Edmunds wrote:
            >[color=green]
            >> I would give the user some iterators:
            >>
            >> template <typename T>
            >> class YourClass {
            >> public:
            >> typedef std::vector<T> t_container;
            >> typedef t_container::co nst_iterator const_iterator;
            >> const_iterator begin() const {return storage_.begin( );}
            >> const_iterator end() const {return storage_.end(); }
            >> ...
            >> };
            >>
            >> A smart client should be able to write his code in such a way that
            >> nothing breaks if you change to a different container type.[/color]
            >
            >
            > Hmm, interesting. I had toyed briefly with that idea, but hadn't
            > formulated any real ideas.
            >
            > I suppose the whole point is not to return the vector, giving access to
            > the iterators, but to return the iterators themselves.[/color]

            Just for a laugh, say I wanted to implement that storage stuff as part of a policy, should I be doing something like the following?

            #include <vector>

            typedef unsigned short uint16;
            typedef unsigned char uint8;

            /**
            * Storage Policy based on a std::vector.
            */
            template<class T>
            class VectorStorage {
            public:
            typedef std::vector<T> t_container;
            typedef typename t_container::si ze_type size_type;
            typedef typename t_container::co nst_iterator const_iterator;

            const_iterator begin() const {return storage_.begin( );}
            const_iterator end() const {return storage_.end(); }
            private:
            t_container storage_;
            };

            /**
            * Storage Policy based on an array.
            */
            template<typena me T>
            class ArrayStorage {
            public:
            typedef T* t_container;
            typedef uint16 size_type;
            typedef const T* const_iterator;

            ArrayStorage(si ze_type size = 8) : size_(size), storage_(new T[size]) {}
            ~ArrayStorage() {delete[] storage_;}

            const_iterator begin() const {return storage_;}
            const_iterator end() const {return storage_ + size_;}
            private:
            size_type size_;
            t_container storage_;
            };

            ....not that I'd want to use an array, usually, but if for some strange reason...

            That would seem to abstract my implementation of storage_ from the user of the class, wouldn't it?

            I could then publicly inherit either of those classes, and specify which one with a template parameter.

            Cheers!

            Ben
            --
            I'm not just a number. To many, I'm known as a String...

            Comment

            • Cy Edmunds

              #7
              Re: Design Question: Inheritance or Accessors

              "Ben Pope" <benpope81@_REM OVE_gmail.com> wrote in message
              news:1122250402 .ba1ed96ef1ac23 d9b40f5f5f364f5 a1b@teranews...[color=blue]
              > Ben Pope wrote:[color=green]
              >> Cy Edmunds wrote:
              >>[color=darkred]
              >>> I would give the user some iterators:
              >>>
              >>> template <typename T>
              >>> class YourClass {
              >>> public:
              >>> typedef std::vector<T> t_container;
              >>> typedef t_container::co nst_iterator const_iterator;
              >>> const_iterator begin() const {return storage_.begin( );}
              >>> const_iterator end() const {return storage_.end(); }
              >>> ...
              >>> };
              >>>
              >>> A smart client should be able to write his code in such a way that
              >>> nothing breaks if you change to a different container type.[/color]
              >>
              >>
              >> Hmm, interesting. I had toyed briefly with that idea, but hadn't
              >> formulated any real ideas.
              >>
              >> I suppose the whole point is not to return the vector, giving access to
              >> the iterators, but to return the iterators themselves.[/color][/color]

              The point is to make your classes maintainable by not putting implementation
              details (such as which container you decided to use) right at the interface.
              For instance if you started with std::vector but later decided to switch to
              ArrayStorage no client code should break.
              [color=blue]
              >
              > Just for a laugh, say I wanted to implement that storage stuff as part of
              > a policy, should I be doing something like the following?
              >
              > #include <vector>
              >
              > typedef unsigned short uint16;
              > typedef unsigned char uint8;
              >
              > /**
              > * Storage Policy based on a std::vector.
              > */
              > template<class T>
              > class VectorStorage {
              > public:
              > typedef std::vector<T> t_container;
              > typedef typename t_container::si ze_type size_type;
              > typedef typename t_container::co nst_iterator const_iterator;
              >
              > const_iterator begin() const {return storage_.begin( );}
              > const_iterator end() const {return storage_.end(); }
              > private:
              > t_container storage_;
              > };
              >
              > /**
              > * Storage Policy based on an array.
              > */
              > template<typena me T>
              > class ArrayStorage {
              > public:
              > typedef T* t_container;
              > typedef uint16 size_type;[/color]

              Hm, just wondering why you would limit your array size to 32K elements. I
              would probably use size_t here.
              [color=blue]
              > typedef const T* const_iterator;
              >
              > ArrayStorage(si ze_type size = 8) : size_(size), storage_(new T[size])
              > {}[/color]

              You should declare this one explicit -- you don't really want automatic
              conversion from uint16 to ArrayStorage<T> .
              [color=blue]
              > ~ArrayStorage() {delete[] storage_;}[/color]

              Now you must include
              ArrayStorage(co nst ArrayStorage<T> &);
              and
              ArrayStorage &operator = (const ArrayStorage<T> &);

              Otherwise innocent looking code like
              ArrayStorage<in t> a1(3);
              ArrayStorage<in t> a2(a1);

              will cause undefined behavior when storage_ gets deleted twice. Boost has a
              reference counted smart pointer for arrays which might do what you want as
              far as copy semantics are concerned.
              [color=blue]
              >
              > const_iterator begin() const {return storage_;}
              > const_iterator end() const {return storage_ + size_;}
              > private:
              > size_type size_;
              > t_container storage_;
              > };
              >
              > ...not that I'd want to use an array, usually, but if for some strange
              > reason...
              >
              > That would seem to abstract my implementation of storage_ from the user of
              > the class, wouldn't it?
              >
              > I could then publicly inherit either of those classes, and specify which
              > one with a template parameter.[/color]

              Better yet, you could encapsulate this object in your new object. This one
              wasn't really made as a base class. For instance, no virtual destructor. I
              think encapsulation should generally be preferred over inheritance with a
              concrete data type like this.
              [color=blue]
              >
              > Cheers!
              >
              > Ben
              > --
              > I'm not just a number. To many, I'm known as a String...[/color]

              --
              Cy



              Comment

              • Dan Cernat

                #8
                Re: Design Question: Inheritance or Accessors



                Ben Pope wrote:[color=blue]
                > Dan Cernat wrote:[color=green]
                > > "Ben Pope" <benpope81@_REM OVE_gmail.com> wrote in message
                > > news:1122234898 .3f87a0a1ac3b33 b90f608e8290e7d f8e@teranews...
                > >[color=darkred]
                > >>Is it preferable to provide an accessor for the private data, e.g.:
                > >>
                > >>std::vector<T > GetData() const {
                > >> return storage_;
                > >>}
                > >>[/color]
                > >
                > >
                > > I would return const reference to data. This way one cannot change it.
                > >
                > > const std::vector<T>& GetData() const
                > > {
                > > return storage_;
                > > }[/color]
                >
                > You'd prefer const reference over copy by value?
                >
                > My original question was going to be over this particular issue, but I thought by-value was generally preferred, and then I thought of the inheritance thing, and wondered about
                > that "more".
                >
                > Cheers,
                >
                > Ben
                > --
                > I'm not just a number. To many, I'm known as a String...[/color]

                think of std::string::c_ str() method. It returns a pointer to the
                internal data.

                Dan

                Comment

                • Martin Eisenberg

                  #9
                  Re: Design Question: Inheritance or Accessors

                  Ben Pope wrote:
                  [color=blue]
                  > Is it possible to return a "forward container"? I don't see
                  > that as some concrete interface or base class anywhere in the
                  > STL, I presume it's "just" a concept, not tangible?
                  >
                  > I suppose I could enforce a similar effect by doing:
                  >
                  > typedef t_container::fo rward_iterator forward_iterato r;
                  >
                  > And if I wanted to be able to do:
                  > *(YourClass.beg in() + 4);
                  >
                  > typedef t_container::ra ndom_access_ite rator
                  > random_access_i terator;[/color]

                  The standard containers don't have such members. Rather, a given
                  container type's iterators belong to a specific category (and its
                  restrictions). Read http://www.sgi.com/tech/stl/iterator_tags.html.
                  If you want your class always to provide iterators of a given
                  category, you will have to adapt in the storage policy for a
                  container type with less capable iterators.


                  Martin

                  --
                  Quidquid latine dictum sit, altum viditur.

                  Comment

                  Working...