Nested immutable classes with records - java

Consider the below arrangement:
interface Message<T> {
T getBody();
}
record SenderAwareMessage<T>(T body, Sender sender) implements Message<T> {
#Override
public T getBody() {
return body;
}
}
record PriorityMessage<T>(T body, Sender sender, int priority) extends SenderAwareMessage<T> {
// cannot extend another record
}
I have 2 concrete implementations here. I want to avoid mutability but at the same time need more configurability at least to extend an existing record. I have went through the documentation, however the usage of record is very limited.
Is there any way I can achieve this scenario with records / any third party libraries?
I tried immutables, but it seems to be an overkill.

Related

What is the point of a “sealed interface” in Java?

Sealed classes and sealed interfaces were a preview feature in Java 15, with a second preview in Java 16, and now proposed delivery in Java 17.
They have provided classic examples like Shape -> Circle, Rectangle, etc.
I understand sealed classes: the switch statement example provided makes sense to me. But, sealed interfaces are a mystery to me. Any class implementing an interface is forced to provide definitions for them. Interfaces don't compromise the integrity of the implementation because the interface is stateless on its own. Doesn't matter whether I wanted to limit implementation to a few selected classes.
Could you tell me the proper use case of sealed interfaces in Java 15+?
Basically to give a sealed hierarchy when there is no concrete state to share across the different members. That's the major difference between implementing an interface and extending a class - interfaces don't have fields or constructors of their own.
But in a way, that isn't the important question. The real issue is why you would want a sealed hierarchy to begin with. Once that is established it should be clearer where sealed interfaces fit in.
(apologies in advance for the contrived-ness of examples and the long winded-ness)
1. To use subclassing without "designing for subclassing".
Lets say you have a class like this, and it is in a library you already published.
public final class Airport {
private List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
public void bookPeople(String... names) {
for (String name : names) {
this.bookPerson(name);
}
}
public int peopleBooked() {
return this.peopleBooked.size();
}
}
Now, you want to add a new version to your library that will print out the names of people booked as they are booked. There are several possible paths to do this.
If you were designing from scratch, you could reasonably replace the Airport class with an Airport interface and design the PrintingAirport to compose with a BasicAirport like so.
public interface Airport {
void bookPerson(String name);
void bookPeople(String... names);
int peopleBooked();
}
public final class BasicAirport implements Airport {
private final List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
#Override
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
#Override
public void bookPeople(String... names) {
for (String name : names) {
this.bookPerson(name);
}
}
#Override
public int peopleBooked() {
return this.peopleBooked.size();
}
}
public final class PrintingAirport implements Airport {
private final Airport delegateTo;
public PrintingAirport(Airport delegateTo) {
this.delegateTo = delegateTo;
}
#Override
public void bookPerson(String name) {
System.out.println(name);
this.delegateTo.bookPerson(name);
}
#Override
public void bookPeople(String... names) {
for (String name : names) {
System.out.println(name);
}
this.delegateTo.bookPeople(names);
}
#Override
public int peopleBooked() {
return this.peopleBooked.size();
}
}
This isn't doable in our hypothetical though because the Airport class already exists. There are going to be calls to new Airport() and methods that expect something of type Airport specifically that can't be kept in a backwards compatible way unless we use inheritance.
So to do that pre-java 15 you would remove the final from your class and write the subclass.
public class Airport {
private List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
public void bookPeople(String... names) {
for (String name : names) {
this.bookPerson(name);
}
}
public int peopleBooked() {
return this.peopleBooked.size();
}
}
public final class PrintingAirport extends Airport {
#Override
public void bookPerson(String name) {
System.out.println(name);
super.bookPerson(name);
}
}
At which point we run into one of the most basic issues with inheritance - there are tons of ways to "break encapsulation". Because the bookPeople method in Airport happens to call this.bookPerson internally, our PrintingAirport class works as designed, because its new bookPerson method will end up being called once for every person.
But if the Airport class were changed to this,
public class Airport {
private List<String> peopleBooked;
public Airport() {
this.peopleBooked = new ArrayList<>();
}
public void bookPerson(String name) {
this.peopleBooked.add(name);
}
public void bookPeople(String... names) {
for (String name : names) {
this.peopleBooked.add(name);
}
}
public int peopleBooked() {
return this.peopleBooked.size();
}
}
then the PrintingAirport subclass won't behave correctly unless it also overrided bookPeople. Make the reverse change and it won't behave correctly unless it didn't override bookPeople.
This isn't the end of the world or anything, its just something that needs to be considered and documented - "how do you extend this class and what are you allowed to override", but when you have a public class open to extension anyone can extend it.
If you skip documenting how to subclass or don't document enough its easy to end up in a situation where code you don't control that uses your library or module can depend on a small detail of a superclass that you are now stuck with.
Sealed classes let you side step this by opening your superclass up to extension only for the classes you want to.
public sealed class Airport permits PrintingAirport {
// ...
}
And now you don't need to document anything to outside consumers, just yourself.
So how do interfaces fit in to this? Well, lets say you did think ahead and you have the system where you are adding features via composition.
public interface Airport {
// ...
}
public final class BasicAirport implements Airport {
// ...
}
public final class PrintingAirport implements Airport {
// ...
}
You might not be sure that you don't want to use inheritance later to save some duplication between the classes, but because your Airport interface is public you would need to make some intermediate abstract class or something similar.
You can be defensive and say "you know what, until I have a better idea of where I want this API to go I am going to be the only one able to make implementations of the interface".
public sealed interface Airport permits BasicAirport, PrintingAirport {
// ...
}
public final class BasicAirport implements Airport {
// ...
}
public final class PrintingAirport implements Airport {
// ...
}
2. To represent data "cases" that have different shapes.
Lets say you send a request to a web service and it is going to return one of two things in JSON.
{
"color": "red",
"scaryness": 10,
"boldness": 5
}
{
"color": "blue",
"favorite_god": "Poseidon"
}
Somewhat contrived, sure, but you can easily imagine a "type" field or similar that distinguishes what other fields will be present.
Because this is Java, we are going to want to map the raw untyped JSON representation into classes. Lets play out this situation.
One way is to have one class that contains all the possible fields and just have some be null depending.
public enum SillyColor {
RED, BLUE
}
public final class SillyResponse {
private final SillyColor color;
private final Integer scaryness;
private final Integer boldness;
private final String favoriteGod;
private SillyResponse(
SillyColor color,
Integer scaryness,
Integer boldness,
String favoriteGod
) {
this.color = color;
this.scaryness = scaryness;
this.boldness = boldness;
this.favoriteGod = favoriteGod;
}
public static SillyResponse red(int scaryness, int boldness) {
return new SillyResponse(SillyColor.RED, scaryness, boldness, null);
}
public static SillyResponse blue(String favoriteGod) {
return new SillyResponse(SillyColor.BLUE, null, null, favoriteGod);
}
// accessors, toString, equals, hashCode
}
While this technically works in that it does contain all the data, there isn't all that much gained in terms of type-level safety. Any code that gets a SillyResponse needs to know to check the color itself before accessing any other properties of the object and it needs to know which ones are safe to get.
We can at least make the color an enum instead of a string so that code shouldn't need to handle any other colors, but its still far less than ideal. It gets even worse the more complicated or more numerous the different cases become.
What we ideally want to do is have some common supertype to all the cases that you can switch on.
Because its no longer going to be needed to switch on, the color property won't be strictly necessary but depending on personal taste you can keep that as something accessible on the interface.
public interface SillyResponse {
SillyColor color();
}
Now the two subclasses will have different sets of methods, and code that gets either one can use instanceof to figure out which they have.
public final class Red implements SillyResponse {
private final int scaryness;
private final int boldness;
#Override
public SillyColor color() {
return SillyColor.RED;
}
// constructor, accessors, toString, equals, hashCode
}
public final class Blue implements SillyResponse {
private final String favoriteGod;
#Override
public SillyColor color() {
return SillyColor.BLUE;
}
// constructor, accessors, toString, equals, hashCode
}
The issue is that, because SillyResponse is a public interface, anyone can implement it and Red and Blue aren't necessarily the only subclasses that can exist.
if (resp instanceof Red) {
// ... access things only on red ...
}
else if (resp instanceof Blue) {
// ... access things only on blue ...
}
else {
throw new RuntimeException("oh no");
}
Which means this "oh no" case can always happen.
An aside: Before java 15 to remedy this people used the "type safe visitor" pattern. I recommend not learning that for your sanity, but if you are curious you can look at code ANTLR generates - its all a large hierarchy of differently "shaped" data structures.
Sealed classes let you say "hey, these are the only cases that matter."
public sealed interface SillyResponse permits Red, Blue {
SillyColor color();
}
And even if the cases share zero methods, the interface can function just as well as a "marker type", and still give you a type to write when you expect one of the cases.
public sealed interface SillyResponse permits Red, Blue {
}
At which point you might start to see the resemblance to enums.
public enum Color { Red, Blue }
enums say "these two instances are the only two possibilities." They can have some methods and fields to them.
public enum Color {
Red("red"),
Blue("blue");
private final String name;
private Color(String name) {
this.name = name;
}
public String name() {
return this.name;
}
}
But all instances need to have the same methods and the same fields and those values need to be constants. In a sealed hierarchy you get the same "these are the only two cases" guarantee, but the different cases can have non-constant data and different data from each other - if that makes sense.
The whole pattern of "sealed interface + 2 or more record classes" is fairly close to what is intended by constructs like rust's enums.
This also applies equally to general objects that have different "shapes" of behaviors, but they don't get their own bullet point.
3. To force an invariant
There are some invariants, like immutability, that are impossible to guarantee if you allow subclasses.
// All apples should be immutable!
public interface Apple {
String color();
}
public class GrannySmith implements Apple {
public String color; // granny, no!
public String color() {
return this.color;
}
}
And those invariants might be relied upon later on in the code, like when giving an object to another thread or similar. Making the hierarchy sealed means you can document and guarantee stronger invariants than if you allowed arbitrary subclassing.
To cap off
Sealed interfaces more or less serve the same purpose as sealed classes, you just only use concrete inheritance when you want to share implementation between classes that goes beyond what something like default methods can give.
Although interfaces have no state themselves, they have access to state, eg via getters, and may have code that does something with that state via default methods.
Therefore the reasoning supporting sealed for classes may also be applied to interfaces.
Suppose you write an authentication library, containing an interface for password encoding, ie char[] encryptPassword(char[] pw). Your library provides a couple of implementations the user can choose from.
You don't want him to be able to pass in his own implementation that might be insecure.
Could you tell me the proper use case of sealed interfaces in Java
15+?
I wrote some experimental code and a supporting blog to illustrate how sealed interfaces could be used to implement an ImmutableCollection interface hierarchy for Java that provides contractual, structural and verifiable immutability. I think this could be a practical use case for sealed interfaces.
The example includes four sealed interfaces: ImmutableCollection, ImmutableSet, ImmutableList and ImmutableBag. ImmutableCollection is extended by ImmutableList/Set/Bag. Each of the leaf interfaces permits two final concrete implementations. This blog describes the design goal of restricting the interfaces so developers cannot implement "Immutable" interfaces and provide implementations that are mutable.
Note: I am a committer for Eclipse Collections.
Interfaces are not always entirely defined by their API alone. Take, for example ProtocolFamily. This interface would be easy to implement, considering its methods, but the result would not be useful regarding the intended semantics, as all methods accepting ProtocolFamily as input would just throw UnsupportedOperationException, in the best case.
This is a typical example for an interface that would be sealed if that feature existed in earlier versions; the interface is intended to abstract the implementations exported by a library, but not to have implementations outside that library.
The newer type ConstantDesc mentions that intention even explicitly:
Non-platform classes should not implement ConstantDesc directly. Instead, they should extend DynamicConstantDesc…
API Note:
In the future, if the Java language permits, ConstantDesc may become a sealed interface, which would prohibit subclassing except by explicitly permitted types.
Regarding possible use cases, there is no difference between a sealed abstract class and a sealed interface, but the sealed interface still allows implementors extending different classes (within the limits set by the author). Or being implemented by enum types.
In short, sometimes, interfaces are used to have the least coupling between a library and its clients, without the intention of having client-side implementations of it.
Since Java introduced records in version 14, one use case for sealed interfaces will certainly be to create sealed records. This is not possible with sealed classes, because records cannot extend a class (much like enums).
Before java 15 developers used to think in a way that code reusability is the goal. But it's not true to all extents, in some cases we want wide accessibility but not extensibility for better security and also codebase management.
This feature is about enabling more fine-grained inheritance control in Java. Sealing allows classes and interfaces to define their permitted subtypes.
The sealed interface allows us to enable it to reason clearly all the classes that can implement it.

OO design - is this design flawed?

I am creating a logic for web application to managing consents from user.
The model class that is persisted in the DB will have multiple fields, from which only a set will be changed with user request. E. g. class will have 10 fields with various consents, but user will be willing to change only 2 of those. To avoid writing a big chain of if-else's I designed this classes, to harness polymorphism to do the job for me, but somehow this design seems flawed to me. Could you tell me if this is proper way to do it?
PROBLEM: Change values of only subset of fields from large set of fields in class.
For sake of simplicity I removed getter/setters methods and some fields.
Main logic for changing consents:
public class GdprServiceImpl implements GdprService {
private final ConsentRepository consentRepository;
#Autowired
public GdprServiceImpl(ConsentRepository consentRepository) {
this.consentRepository = consentRepository;
}
#Override
public void changeConsent(User user, List<ConsentDto> consents) {
Optional<Consent> optionalConsent = consentRepository.findByUser(user);
if(optionalConsent.isPresent()) {
Consent consent = optionalConsent.get();
for(ConsentDto consentDto : consents) {
consentDto.apply(consent);
}
consentRepository.save(consent);
}
else {
Consent consent = new Consent();
consent.setUser(user);
for(ConsentDto consentDto : consents) {
consentDto.apply(consent);
}
consentRepository.save(consent);
}
}
Model class:
public class Consent {
private Boolean messageConsent;
private Boolean recordConsent;
/*CONSTRUCTOR, OTHER METHODS AND FIELDS OMITTED*/
}
Classes that will change a set of fields from Consent class:
public abstract class ConsentDto {
public abstract void apply(Consent consent);
}
public class RecordConsentDto extends ConsentDto {
private boolean consentValue;
public RecordConsentDto(boolean consentValue) {
this.consentValue = consentValue;
}
#Override
public void apply(Consent consent) {
consent.setRecordConsent(consentValue);
}
}
public class MessageConsentDto extends ConsentDto {
private boolean consentValue;
public MessageConsentDto(boolean consentValue) {
this.consentValue = consentValue;
}
#Override
public void apply(Consent consent) {
consent.setMessageConsent(this.consentValue);
}
}
You are right about the design having a "smell".
This is because the DB design is not normalized.
having a list of consents in one record is an indication. while technically it is allowed, classic RDBMS design dictatets that arrays should be represented as either one-to-many or many-to-many relation between tables. Of course, same in the object model.
a Fully normalized solution will have a consent_catalog table and many-to-many relation to users:
table consent_catalog {
int id // PK
String name
}
The catalog acts as "consent enum", having one row per type of consent (record, message, etc)
table user_consents {
int user_id references users(id)
int consent_id references consent_catalog(id)
}
This table has rows only for consents accepted by the user. no "false" consents. This design opens up new possibilities like knowing which users have a specific consent or mulitple consents in common.
This design feels like an overkill. At the end of the day you are always calling consent.setMessageConsent() or similar it's wrapped with an enum field and a class implementing ConsumerDto (which is really a Consumer). Generally DTO are not supposed to implement business logic yet one could argue that apply method is one.
It really would be cleaner to have UserConsent POJO with Boolean fields. The exception would be if triggering one consent should trigger other but it's not clear from your example.
Just my two cents. I'd prefer to see either an anemic POJO passed around or DDD aggregate root for user that manages consents but not something in between.

Which design pattern is recommended when implementations only differ in a single method?

I have an interface with 6 methods used to manage datasets. The only method that differs between implementations is getSerializedVersion() and the constructor that is able to parse the serialization string.
public interface DataSets {
public void addEntry(...);
public void removeEntry(...);
public void manipulateEntry(...);
public SomeType getEntry(...);
public List<SomeType> getAllEntries();
// This differs:
public String getSerializedVersion()
}
I can't change the Interface.
My first idea was to generate an abstract class and implement the first five methods. For the concrete implementations (e.g. DataSetsXML, DataSetsYAML, ...) I only have to implement getSerializedVersion() and the constructor that that is able to read the String and initialize the object.
To make it more testable a different design might be better (https://stackoverflow.com/a/7569581) but which one?
Answers might be subjective, but I think there are some general rules or a least (objective) advantages and disadvantages of the different approaches,...
From what you explain the difference is something that is not related to the behavior of the class but just how it is serialized and unserialized. What I mean is that the DataSetsXML and DataSetsYAML would have the same identical funcionality but they would be serialized into different formats.
This means that there is no benefit in keeping getSerializedVersion() coupled with the DataSets class. You should totally decouple them.
You could have a serialization interface sort of:
interface DataSetsSerializer
{
public DataSets unserialize(String string);
public String serialize(DataSets sets);
}
and then take care of differente implementations just in this class, eg:
class YAMLDataSetsSerializer implements DataSetsSerializer
{
public DataSets unserialize(String string) {
DataSets sets = new DataSets();
...
}
public String serialize(DataSets sets) {
...
}
}
By elaborating on JB Nizet comment, if you have to keep a DataSetsSerializer inside a DataSets instance (which IMHO makes no sense since they should be decoupled in any case, as a specific way of serialization shouldn't be bound to the data to be serialized) then the approach would be the following:
class DataSets {
final private DataSetsSerializer serializer;
public DataSets(DataSetsSerializer serializer, String data) {
this.serializer = serializer;
serializer.unserialize(this, data);
}
#Override
public String getSerializedVersion() {
return serializer.serialize(this);
}
}
This requires a slight change in the proposed interface and it's not a clever design but it respects your requirements.
I think it is reasonable to use an abstract class. You can test the concrete implementations of the abstract class (which indirectly tests the abstract class as well).

Calling different services from domain classes of same base class

The question is mostly a design question (somewhat related to ddd). Sorry about the contrived example:
Assume, you have (domain) classes representing different types of fruits: apple, cherry and so on. Now suppose you have to implement some behavior of pressing out the juice. A caller should be able to invoke squeezing without knowing which specific fruit he's got.
Where should I put this behavior?
Surely, one could define a fruit interface / base class function
Fruit#squeeze()
and let all subclasses implement their own behavior.
Now a caller could simply do something like this:
Fruit f = new Cherry();
f.squeeze();
But what should be done if squeezing isn't as simple and involves more complex behavior like calling different external services, for each a fruit a different one like
AppleJuicerService#squeeze(Apple a)
and
CherryJuicerService#squeeze(Cherry c)
? It feels wrong to call services from a domain class.
I've read about the double dispatch pattern which seems not to fit here, as every subclass needs a different service.
My question would be: What can be done here to get a "clean" design?
EDIT:
Thanks for all your answers so far. I'll try to clarify the problem a bit. I'll try to give another, hopefully less contrived example for the problem I'm trying to state here:
Consider a Message base class which allows to show its content as a String.
interface Message {
String showContent();
}
Now suppose we have different types of messages like an EMailMessage:
class EMailMessage implements Message {
//some specific parameters for email
private EmailAddress recipientEmail;
public String showContent() {
//here the content would be converted to string
return "the content of an EMail"
}
}
Another type would be an SMSMessage:
class SMSMessage implement SMSMessage {
//some specific parameters for SMS
private TelNumber recepientTelephoneNumber;
public String showContent() {
//here the content would be converted to string
return "the content of a SMS"
}
}
Furthermore suppose, Messages are modeled as Entities and therefore can be persisted in a database. Though quite technically, assume that some Dependency Injection Framework like Spring is used to inject dependencies.
In analogy to the fruit example, consider we have to implement a send() behaviour which sends the Message to the recipient. Furthermore, assume that sending an EMail involves different logic than an SMS. Now, the question: Where should one put the logic of sending a Message?
Usually I'd opt to create a service for sending an SMS for example which would encapsulate e.g. the API of an SMS service provider. Furthermore, I'd create another service to encapsulate sending an EMail.
interface SendMessageService<T extends Message> {
void send(T message);
}
class SendEmailService extends SendMessageService<EMailMessage> {
public void send(EMailMessage message) {
//send the EMail
}
}
class SendSMSService extends SendMessageService<SMSMessage> {
public void send(SMSMessage message) {
//send the SMS
}
}
The drawback of this approach is that you cannot send a Message without determining its concrete subclass, i.e. something like the following is not directly possible
List<Message> messages = //Messages of different types
SendMessageService service = //???
for (Message m : messages) {
service.send(m);
}
Surely one could create a factory for creating Services according to the specific type of message. But that somewhat means cloning the inheritance hierarchy of Message. Is there some better way to achieve the desired result? Or am I missing something? Or would it be better to somehow inject the service into the entity?
You can delegate the work to a SqueezeBehavior interface and let each implementation define how to squeeze a Fruit or specific Fruit. This is a raw idea (it means it can be improved but is good as a first step):
interface SqueezeBehavior<T> {
void squeeze(T squeezeMe);
}
interface FruitSqueezeBehavior<T extends Fruit> extends SqueezeBehavior<T> {
}
class FruitSqueezer implements FruitSqueezeBehavior<Fruit> {
public void squeeze(Fruit fruit) {
System.out.println("squizing any fruit");
}
}
class AppleSqueezer implements FruitSqueezeBehavior<Apple> {
public void squeeze(Apple apple) {
System.out.println("squizing apple");
}
}
class CherrySqueezer implements FruitSqueezeBehavior<Cherry> {
public void squeeze(Cherry cherry) {
System.out.println("squizing cherry");
}
}
class FruitService {
public void foo(Fruit fruit) {
FruitSqueezeBehavior fruitSqueezer = ...
fruitSqueezer.squeeze(fruit);
}
}
Have a baseclass Fruit which defines the standard behaviour. When you have to use a more complex implementation you can override the appropriate method.
class Fruit {
public void Squeeze(){
// Standard squeeze behaviour
}
}
class Apple extends Fruit {
#Override
public void Squeeze(){
// Complex squeeze behaviour
}
}
class Cherry extends Fruit {
// Nothing special, cherries are easy to squeeze
}
If you have to define specific implementations for specific types, you will always have to define the behaviour somewhere. If this is too much for one method then you can call a more detailed class to do it for you.
You could work with a factory and do something like this
class FruitManipulator {
void Squeeze(Fruit f){
// Switch over fruit, create new service depending on the type
}
}
interface JuiceService<T extends Fruit> {
void Squeeze(T f);
}
class AppleJuiceService implements JuiceService<Apple> {
void Squeeze(Apple apple){
// Do your thing
}
}
And use it like this:
FruitManipulator service = new FruitManipulator();
service.Squeeze(new Apple());
You might want to find a better example though: the Squeeze() analogy isn't easy to work with. Perhaps expand on what a squeeze actually means?
You may consider DomainEvents. This helps you decouple Domain models from external service(usually stateless bean need injected)
interface Fruit {
void squeeze();
}
class Apple implements Fruit {
#Override
public void squeeze(){
// domain rules validations
DomainEvents.raise(new AppleSequeezedEvent(this));
}
}
class Cherry extends Fruit {
#Override
public void squeeze(){
// domain rules validations
DomainEvents.raise(new CherrySequeezedEvent(this));
}
}
class Banana extends Fruit {
#Override
public void squeeze(){
// domain rules validations
// hmm...No one cares banana...
}
}
class DomainEvents {
private static List<DomainEventHandler> handlers = new ArrayList<DomainEventHandler>();
public static void register(DomainEventHandler handler) {
this.handler.add(handler);
}
public static void raise(DomainEvent event) {
for (DomainEventHander handler: handlers) {
if (handler.subscribe(event.getClass()) {
handler.handle(event);
}
}
}
}
Now when you test apple, you could register some handler mock/stub:
#Test
public void tellsAppleIsSqueezed() throws Throwable {
DomainEventHandler stub = new FruitSqueezedEventHandlerStub(Apple.class);
DomainEvents.register(stub );
Apple apple = new Apple();
apple.squeeze();
//assert state change of apple if any before you publishing the event
assertThat(stub.getSqueezed(), sameInstance(apple));
}
You can test the real handler in their own unit test cases.
But I think this solution add extra complexity.

Design approach (Domain driven or Service Driven)

My problem statement is :
I want to write design file management (add, copy, delete etc. operations). There are two approach :
Service Driven approach
Write file VO which contains only file attributes. For e.g.
public Class File {
private boolean hidden;
private boolean read;
private boolean write;
public boolean isHidden() {
return hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public boolean isWrite() {
return write;
}
public void setWrite(boolean write) {
this.write = write;
}
}
and separates service for File related operations. For e.g. :
public Class FileService {
public boolean deleteFile(File file) {
//Add delete logic.
}
//Same way you can add methods for Add and copy file.
}
Domain Driven approach (I might be wrong here.)
Where file VO contains all the attributes plus required operations :
public class File {
private boolean hidden;
private boolean read;
private boolean write;
public boolean isHidden() {
return hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public boolean isWrite() {
return write;
}
public void setWrite(boolean write) {
this.write = write;
}
public boolean deleteFile() {
//Add delete logic.
}
//Same way you can add methods for Add and copy file.
}
So what are the pros and cons of both the approach ?
In an object oriented language, putting the logic in the class itself, rather than a service class, is the typical approach (and better IMO). It follows the "tell, don't ask" principle, for example, by telling a File to delete itself, rather than asking some service to delete it. One of the main reasons behind this is to allow for inheritance. For example, if you have a subclass of File and wanted to have it write a log message before it was deleted, that would be difficult to do with a service class because you would need a different service class for every subclass.
In terms of a service-oriented approach, this is typically thought of at a higher level (i.e. a service oriented architecture). Consider a financial stock system, you might have a "buy stock" service and a "sell stock" service. Have a service class that corresponds to individual classes (i.e. a Stock service, which knows how to buy and sell stocks) wouldn't be very object oriented.
You might also have a service layer in your system, which provides integration points with other external services (i.e. a database), but again, I don't think this is what you're talking about here. So, I could stick with the approach of encapsulating the logic in the File class itself.
Without much information about what kind of system your are desigining it's hard to pronounce. To me, the choice depend on the system boundaries.
If you need to offer an API that is exposed as a service and accessible to external consumer, go for solution 1, that's the only way. If you system is rather an library whose API will be used internally by other applications, go for a rich domain model as in solution 2, it's a lot more OO. You don't want to bloat your API with service-, manager-, and utility classes for which no real reason exist.
But again, without knowing your final goal, it's hard to say.

Categories

Resources