The distinction between Entity and Value object should be based around the question: If I have two objects with the same contents (two AdvertisementEvents linking to the same Banner with the same parameters), should I treat them differently or can one be replaced by the other without affecting how the software works?
In this case, I would say that you can replace one AdvertisementEvent by another with the same values without affecting the operation of the software. This makes them Value objects (the contained value is what counts, not the identity of the object itself).
As for the size of a Value object: As long as it contains a coherent set of parameters for a single responsibility, there is no limit on how large a Value object can be. In the implementation it might be good to pay special attention to large value objects to ensure they are not needlessly and excessively copied but otherwise it is no problem.
As for the constraints on the number of AdvertisementEvents you have within a Movie, this is a constraint on the relation between a Movie and its collection of AdvertisementEvents, not on one of those classes individually. As such, the most logical place to enforce the constrained is at the point where the collection gets maintained in Movie (thus in the method where you try to add an AdvertisementEvent).
Alright, let's go one by one.
Values
Values are the concrete pieces of data that programs evaluate and juggle. Nothing fancy, some examples might be
1
true
"fizz buzz foo bar"
Types
A nice description for a type is "a classifier for a value". A type is a little bit of information about what that value will be at runtime, but indicated at compile time.
For example if you tell me that e : bool
at compile time, and I'll know that e
is either true
or false
during runtime, nothing else! Because types classify values nicely like this, we can use this information to determine some basic properties of your program.
For example, if I ever see you adding e
and e'
when e : int
and e' : String
, then I know something is a bit off! In fact I can flag this and throw an error at compile time, saying "Hey, that doesn't make any sense at all!".
A more powerful type system allows for more interesting types which classify more interesting values. For example, let's consider some function
f = fun x -> x
It's pretty clear that f : Something -> Something
, but what should that Something
be? In a boring type system, we'd have to specify something arbitrary, like Something = int
. In a more flexible type system, we could say
f : forall a. a -> a
That is to say "for any a
, f
maps an a
to an a
". This let's us use f
more generally and write more interesting programs.
Moreover, the compiler is going to check actually satisfying the classifier we've given it, if f = fun x -> true
then we have a bug and the compiler will say so!
So as a tldr; a type is a compile time constraint on the values an expression can be at runtime.
Type Constructor
Some types are related. For example a list of integers is very similar to a list of strings. This is almost like how sort
for integers is almost like sort
for strings. We can imagine a sort of factory that builds these almost-the-same types by generalizing over their differences and building them upon demand. That's what a type constructor is. It's kind of like a function from types to types, but a little more limited.
The classic example is a generic list. A type constructor for is just the generic definition
data List a = Cons a (List a) | Nil
Now List
is a function which maps a type a
to a list of values of that type! In Java-land I think these are perhaps called "generic classes"
Type Parameters
A type parameter is just the type passed to a type constructor (or function). Just like in the value level we'd say foo(a)
has a parameter a
just like how List a
has a type parameter a
.
Kinds
Kinds are a bit tricky. The basic idea is that certain types are similar. For example, we have all the primitive types in java int
, char
, float
... which all behave as if they have the same "type". Except, when we're speaking of the classifiers for types themselves, we call the classifiers kinds. So int : Prim
, String : Box
, List : Boxed -> Boxed
.
This system gives nice concrete rules about what sort of types we can use where, just like how types govern values. It'd clearly be nonsense to say
List<List>
or
List<int>
In Java since List
needs to be applied to a concrete type to be used like that! If we look at their kinds List : Boxed -> Boxed
and since Boxed -> Boxed /= Boxed
, the above is a kind error!
Most of the time we don't really think about kinds and just treat them as "common sense", but with fancier type systems it's something important to think about.
A little illustration of what I've been saying so far
value : type : kind : ...
true : bool : Prim : ...
new F() : Foo : Boxed : ...
Better Reading Than Wikipedia
If you're interested in this sort of thing, I'd highly recommend investing a good textbook. Type theory and PLT in general is pretty vast and without a coherent base of knowledge you (or at least I) can wander around without getting anywhere for months.
Two of my favorite books are
- Types and Programming Language - Ben Pierce
- Practical Foundations of Programming Languages - Bob Harper
Both are excellent books that introduce what I've just talked about and much more in beautiful, well explained detail.
Best Answer
TL;DR
Note. — The meaning of "value semantics" in the context of programming has drifted, as evidenced by the definitions provided in the appendix. What follows is my attempt to make sense of it all.
Spaces in Memory
Information is stored in spaces in memory where they can be reused. There three spaces in memory:
Of each kind, there can be multiple spaces in memory. For example, multiple arguments. Each a space in memory.
A language/runtime/platform may or may not have any of these. For example, some do not have a stack. Some do not have arrays or composite types. And some do not have a heap. However, they will all have at least heap or stack.
We will not be talking about named constants, literals, immediate values, or the distinction between l-values and r-values.
Variables
In most languages we give names to spaces in memory. This makes it easier to use them. We call these named spaces in memory “variables”.
Going forward, we will refer to the information stored in the space in memory named by a variable as the contents of the variable.
It is also worth noting that the names of the variables may or may not exist in runtime (e.g. reflection), also if they do, their static type information may or may not exist in runtime (e.g. type erasure).
Furthermore, the position in memory of named variable may change.
Note. — What I call here contents, other authors call value. I'm not calling it value, because I'm using Lakos' definition of value. However, I would agree that the contents of a variable is a value. A physical value. While the value that Lakos' talks about is a
platoniclogic value.Types and Instances
A type is a set of memory layout. We will refer to each of each of the possible memory layout of a given type that actually exist in memory as instances. Instances may overlap in memory.
These memory layouts will define the contents of the variable that hold said instances. See “Value Types and Reference Types” below.
Variables and Types
In a dynamically typed language, the contents of the variables can be of any type.
On the other hand, in statically typed languages, the variables have a type, and this type specifies the possible contents of the variable.
Note. — Some statically typed language support typing a variable as dynamic. That is, the type of the variable is “look into the contents of the variable to figure out the type”.
Primitive Types and Composite Types
Composite types are types constructed out of other types. Which is not true for primitive types.
Do not confuse primitive types with build-in types. That is the set of types provided by a languages. As currently plenty of languages provide composite types. Instead primitive types are indivisible within the constraints of the language.
Equality
Considering the instances of a type, we may or may not care about a concept of equality for these instances. That is, equality may or may not be part of the specification/requirements for the type.
We only care about equality, when the type has a concept of “value”.
Values
For types that have a concept of value, the value is derived from the contents of the instances. Or rather, I should say, that the contents represent the value.
However, the contents is not the value. I mean, the equality of the instances does not imply equal representation in memory. This is because there could be multiple representations in memory for the same value. Consider, for example, that in some types there are multiple ways to represent a value in memory, and thus would require canonization/normalization (e.g. strings, date, decimal floating point numbers).
This is also how we can say that values stored in different types have the same value, i.e. are equal (e.g. 5 stored in a short integer vs 5 stored in a long integer).
When dealing with composite types, we would talk about salient attributes.
From the book Large-Scale C++ Volume I: Process and Architecture by John S. Lakos:
Will get to “value-semantic type”.
Only salient attributes are considered part of the value of a type, and which attributes are salient is decided by the specification/requirements for that type, not by the representation in memory.
References
References are variables such that their contents refers to an instance, instead of being an instance. That is, the contents will have a position in memory where an instance is found, instead of containing the instance directly.
What I define above would be pointers in C++. We are not talking about the C++ distinction of pointers and references.
In some platforms there is a garbage collector that may move instances around. When this happens, the garbage collector also has to update the references to them.
Due to composition, we may have instances that have references.
Copy and Move
Since each variable has a space in memory, when we assign a variable to another (assuming their types are compatible) we need to copy the contents. See “Types of Copy” below.
If the types of the variables are not compatible. A conversion is necessary. One special case is when assigning to a reference.
In some cases, we know that a variable will cease to exist. For example, a local variable when returning from a subroutine goes out of scope. If we are returning the local variable, and assigning the returned value to another variable, the compiler may opt to not copy it, but move it instead. Moving here means changing the space in memory named by the variable.
Since move happens only when a variable is ceasing to exist. We do not have to worry about move.
Pass by Reference and Pass by Value
A parameter of a subroutine is a variable. When we call the subroutine, the parameters are assigned. If the parameters are types are references, then we are passing instances by references. Otherwise, they are passing by value. And yes, that is a copy.
Types of Copy
A shallow copy limits itself to copying the contents of a variable. On the other hand, a deep copy would follow references and copy them too. That is, a deep copy is recursive with respect to references.
Please note that these are not the only options when it comes to copy instances. We will come back to that.
For contents that do not include references, a shallow copy is a complete copy. However, for contents that include references, a deep copy is necessary to get a complete copy.
We will understand as complete copy, a copy of the whole memory layout of an instance. If we do not copy the whole, then it is an incomplete copy. If the memory layout does not have references and exists only in the contents of the variable, then a shallow copy is a complete copy. Otherwise, a shallow copy is an incomplete copy.
A shallow copy is the default.
Note. — A variable contents could be a handle to a resource. It could be an external resources, such as a a handle to a window object or a key to a row in a database table. It could also be an internal resource such as an index to an array (See Entity-Component-System). These are not references as defined above, however they can be considered as such (we could say a pointer is a physical reference, while a handle is a logical reference). If the referenced resources are not copied, they may provide a means for instances to affect each other. See "Rule of Three" below. You may also be interested in RAII. My personal opinion we should not try to archive value semantics with that include handles to external resources, and if we were to, it would require to copy those resources too.
Value Types and Reference Types
We find in the C# language reference:
Reference types are types such that variables of that type are references to the instance. That memory layout for reference types defines that the variables hold a reference to the instance.
In C++, only pointers and references are reference types. However, we find plenty of reference types in other languages. For example, Java and .NET classes are reference types. C# structs, by the way, are value types.
On the other hand, value types are types such that variables of that type are not references. In other words, the contents of the variable is the instance.
Do not confuse value types and reference types with value-semantic types and reference-semantic types. Also do not confuse value types with primitive types.
Now, since variables of reference types are references. And a shallow copy is the default. The assignment of reference types results in an incomplete copy… unless the default is overridden.
For value types, the assignment results in a complete copy, if and only, they are not composite types that include references. See also Can structs contain fields of reference types (C#).
Value-Semantic Types and Reference-Semantic Types
A value-semantic type is a type such that copy provides instance independence. That is, the result of the copy should not be usable to mutate the original. Emphasis on copy. This is not about making a reference.
This matches Alexis Gallagher’s Mutation game.
There are two simple ways to accomplish this:
However, in general, you must provide a copy that copies every part of the instance which is not immutable. If the type is immutable, then shallow copy is sufficient. If the type has no immutable parts (and it is a reference type or a value type which includes references) then you must provide a deep copy. If some parts are immutable and some are not, then you can archive value semantics by doing a deep copy of the mutable parts (and shallow copy of the immutable parts, sharing them). Which, by the way, is neither a shallow copy nor a deep copy, but a mixture.
Note. — Bjarne Stroustrup only considers deep and shallow copy when defining value semantics in Programming: Principles and Practice Using C++.
If we have a reference type, which only contains a field of an immutable reference type. Then it is sufficient to copy that reference. There is no need to copy the immutable instance. Then, when implement mutation operations by swapping that reference with a new one. This is copy-on-write.
Value Objects
From the book Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans (who coined the term “value object”):
Evans also had the concern of value semantics:
We see the same definition, and the same concern for value semantics echoed by other authors.
From the book Patterns of Enterprise Application Architecture by Martin Fowler et al.:
See also Value Object.
It is worth noting that Evans also describes Entities, which are objects that have value, are mutable, and have identity.
Beyond that, Evans also describes Services, which are objects that do not have value and are about behavior. A lot of threading constructs are services. For example, a read-write lock. A read-write lock is not a value.
Note. — I’m saying that value objects do not imply value semantics, only value equality. However, value semantics is a desirable feature of value objects. We can say that value objects without value semantics are poorly designed.
Bonus Chatter
Rule of three
This is particular to C++.
Let us say we want value semantics, and we have a value type that has no reference type fields. For this, the default shallow copy is sufficient.
Now, let us say we add a reference type field to our type. And thus, our shallow copy results in two instances with fields pointing to the same instance of the reference type.
To avoid the shallow copy we need to override the assignment operator, and implementing a deep copy. However, if we are not assigning to an existing variable but initializing a new one, the assignment operator does not get called, but the copy constructor instead (and again, the default is shallow copy). Thus, we need to override the copy constructor too.
We run into a similar problem with the default destructor. It will not follow reference. That is, it will not do a deep destruction. Which would mean we would be leaking the instance of the reference type field. Thus, we also need to override the default destructor.
Thus, we want to override the assignment operator, the copy constructor and the destructor. This is not possible in most languages.
See also Rule of three.
On References and Value Semantics
We should not require the concept of references or pointers to define value semantics. Languages that do not have these concepts can still have value semantics.
There is another concept related to value objects we need to talk about: data transfer objects. DTOs are meant to cross boundaries. They might be going to another process, even to another machine. They may not. When crossing these boundaries references do not work. And thus, DTOs must avoid references.
DTOs should have no behavior, and have value semantics.
DTOs are often confused with value objects. Martin Fowler:
Objects
If you go back to the definition of object (according to Grady Booch), you will find that objects have identity (and state and behavior, which could be none). However, we are ignoring this definition, instead we are saying that objects are instances of classes.
Plus, I would argue that the name value object is influence by the fact that Evans was working in Java, and thus could not define custom value types. To reiterate, Value Objects in Java are of reference types.
Thread Safety
Another argument for value semantics is thread safety.
Please note that if we are passing references, even if const references, that could be modified by another thread behind the scenes, we will run into trouble. Thus, any reference must be to an immutable type or a thread safe type.
Your Questions
is an object of value type a value object?
Value objects can be of value types or reference types.
is an object of reference type a reference object?
Instances of reference types would be reference objects, unless they override equality.
does an object of value type have value semantics?
If it does not have reference type fields, or if it overrides the default copy to provide value semantics.
does an object of reference type have reference semantics?
If it is not immutable and does not override the default copy to provide value semantics.
Appendix: Definitions of "Value Semantics", a time line
1998
This template version of List includes a generic iterator and value semantics to store generic data. Value semantics means that List stores instantiated objects, not pointers to objects. During insertion operations, List stores copies of data values instead of storing pointers. Although containers with value semantics allow applications to manage small objects and build-in types easily, many applications cannot tolerate the overhead of copying objects.
– Paul Anderson, Gail Anderson – Navigating C++ and Object-oriented Design
2004
STL containers are value semantic. When a task object is added to an STL container, the task object's allocator and copy constructor are called to clone the original. Similarly, when a task object is removed from an STL container, the task object's deallocator is called to delete the copy. The value semantics may be a performance concern, especially if producers and consumers frequently add tasks to and remove tasks from a queue.
– Ted Yuan – A C++ Producer-Consumer Concurrency Template Library
2004
ValueSemantics for objects-by-value are preserved by copying values between objects. ValueSemantics for objects-by-reference are preserved by using a CopyOnWrite mechanism. I had always thought that the story ended there. Are ValueObjects simply objects that preserve ValueSemantics or is there something more to them?
– PhilGoodwin – Value Objects Can Be Mutable
2014
Types that provide shallow copy (like pointers and references) are said to have pointer semantics or reference semantics (they copy addresses). Types that provide deep copy (like string and vector) are said to have value semantics (they copy the values pointed to). From a user perspective, types with value semantics behave as if no pointers were involved – just values that can be copied. One way of thinking of types with values semantics is that they “work just like integers” as far as copying is concerned.
– Bjarne Stroustrup – Programming: Principles and Practice Using C++
2015
it's (…) possible for a type to be value semantic provided that it keeps one very important property true which is if two objects of the given type have the same value today and we apply in the same salient operation (by salient I mean an operation that is intended to approximate the Platonic type that lives outside of the process that we're using as our model) then after that operation is applied to both objects they will again have the same value or they never did and that is a key property of value semantics.
Another way to say this would be if two objects have the same value then there does not exist a distinguishing sequence of salient operations that will cause them to no longer have the same value.
– John Lakos – An interview with John Lakos
2016
Value semantics amounts to being a guarantee of the independence of the value of variables.
And independence doesn’t mean structural things. What we’re talking about is can one thing affect another. So a type has value semantics if the only way to modify a variable’s value, a variable that has the value semantic type, is through the variable itself. If the only way to modify a variable’s values is through the variable itself, it’s a variable with semantic type.
(…)
The type is value semantic if it’s immune from side effects produced by other things. Not if it’s guaranteed not to perpetrate side effects on other things.
– Alexis Gallagher – Value SEMANTICS (not value types!)