Value vs. Entity Objects

Entities

  • have a unique identity
    • even if they have the same data, so long as the objects are distinct, they are considered different entities
  • have lifespans / continuity; can be changed over time and over implementations
  • generally rely on references or pointers
  • Examples:
    • physical objects: airplane, runway, taxiway, ...
    • people: passenger, booking agent, ...
    • records: customer information, boarding pass, flight schedule, ...
    • transactions: reservations, cancellations, receipts, ...

Values

  • are simply data, and are immutable (eg: Java strings)
    • immutability is faked in C++
      • return a new object by value and over-write (or std::move) of contents to over-write rater than modify this.
  • if the data is the same, we consider two distinct values to be the same
  • Examples:
    • mathematical types: rational numbers, polynomials, matrices, ...
    • measurements: size, distance, weight, mass, energy, duration, ...
    • other quantities: money
    • other properties: colour, location, date, time, ...
    • restricted value sets: names, addresses, postal codes, number ranges, ...

Quick example:

You ware implementing a videogame version of a card game. Which classes are Entity-based ADTs, and which are Value-based ADTs?**

Class Entitiy Value
Score X
Player X
Hand X
Deck X
Card ? X

Design of Entity ADTs

Operations on Entity ADTs should reflect a real-world event:

  • Copying an entity is usually no meaningful, since programs no longer reflect reality. Operations on copies are uncoordinated and can be lost (when copies disappear)
    • prohibit copy constructor*
    • prohibit assignment*
    • prohibit type conversions*
    • avoid equality
    • clone operation may be useful
  • Computations on entities are generally not meaningful
    • think twice before overloading any operators aside from new and delete
    • operator< might be useful to overload as a way to apply to an entity's name or unique ID

* by prohibit we actually mean using the new delete keyword to remove these operations from the class. Eg:

class X {
public:
    X(const X&) = delete;
    X&operator = (const X&) = delete;
} // valid C++11 and up

Design of Value ADTs

Equality is important in value types:

  • quality and other comparison operators are valid and useful
  • copy constructor should exist
  • assignment operator should exist

Computations involving values might make sense

  • consider overloading arithmetic operators

Virtual function and inheritance are generally uncommon (tend to use final on the class)

Mutable Objects

Mutable Value-based ADTs (e.g: Date) are problematic when they can be referenced from two variables.

You can run into errors like this:

Person myPerson ( "David O'Leary", new Date(1, "May", 1990) );
cout << myPerson.DOB() << endl; // assume implemented properly

Date myDate = myPerson.DOB();
myDate.monthIs( myDate.month() + 1 );
cout << myPerson.DOB() << endl; // oof!

Q: How do we make something immutable?

  1. remove all mutators
  2. make data private
  3. make sure class cannot be derived from (using final), or make some methods on the class final
  4. Whenever revieving or returning a ref/ptr, just make a copy

In C++, fake immutability via deep copy + overwriting original object data via operator=

Singleton Design Pattern

Ensures that exactly one object of our ADT exists.

class Egg {
    static Egg e;          // singleton instance
    int i;                 // data member
    Egg(int ii) : i(ii) {} // private constructor
public:
    static Egg* instance() { return &e; }
    int val() const { return i; }

    Egg(const Egg&) = delete;             // prevent copy
    Egg& operator= (const Egg&) = delete; // prevent assign
};

Egg Egg::e(42); // initialization of singleton

Exposed Implementation and the PImpl Idiom

Generally, it's not ideal to have a implementation exposed to client. So, the code below is less than ideal.

class Rational {
public:
    Rational (int numer = 0, int denom = 1);
    int numerator() const;
    int denominator() const;
private:
    int numerator_;
    int denominator_;
};

Using the PImpl Idiom let's us hide the data-implementation of a class from client code.

We simply encapsulate the data representation in a nested private structure (which can be in a separate file).

class Rational {
public:
    Rational (int numer = 0, int denom = 1);
    int numerator() const;
    int denominator() const;
private:
    struct Impl;
    Impl* rat_;
public:
    ~Rational();
    Rational ( const Rational& );
    Rational& operator= ( const Rational& );
};

// later
struct Rational::Impl {
    int numerator_;
    int denominator_;
public:
    Impl(int n, int d): numerator_{n}, denominator_{d} {}
};

// what the Rational constructor might then look like
Rational::Rational(int n, int d):
    rat_{new Rational::Impl{n, d}} {
    if (d == 0) throw "Panic! denominator == 0";
    reduce(); 
    // ...    
}

// must also implement a destructor for Rational that deletes it's Impl

Note: Rational must have pointer to Impl, as we want the precise structure of Impl to be separate from the Rational class. By using a pointer, the compiler doesn't need to know Impl's precise structure, just that we want a pointer to it.

results matching ""

    No results matching ""