i'm working on school project need create basic application manages tasks , projects. follow mvc-pattern our project. focus lays on design of application , our team has been struggling design issue:
encapsulation of data passed between view , controller
this means want make sure view not have references real data. tried fix creating value classes, it's huge workaround. these final classes copies of normal model classes. example if have class project have final class called projectvalue same fields project except final make objects of value class immutable, nothing can changed in view. doesn't feel right kind of duplicate these classes encapsulation, there must easier way.
i'll try explain problem example:
a user wants see projects. therefore start application , click on button labeled "show projects". button initiate method in controller called getall():
public plist<projectvalue> getall() { plist<projectvalue> projects = plist.empty(); (branchoffice office : company.getbranchoffices()) { (project project : office.getprojects()) { projects = projects.plus(project.getvalue()); } } return projects; } first loops on branch offices. every project in branch office takes value object of project (project.getvalue()) , puts in list instead of normal project.
an example of model class , inner value class:
public class resource implements serializable, comparable<resource> { /** * variable registering name resource. */ private string name; private branchoffice office; /** * variable registering resource type resource. */ private resourcetype type; /** * variable registering reservations reserve resource. */ private set<reservation> reservations; /** * initializes resource given name , type. * * @param name * name resource, f.e. audi * @param type * type of resource, f.e. car * @throws invalidresourceexception */ public resource(string name, branchoffice office, resourcetype type) throws invalidresourceexception { try { setname(name); setbranchoffice(office); settype(type); setreservations(null); } catch (invalidrequiredstringexception | invalidrequiredresourcetypeexception e) { throw new invalidresourceexception(e.getmessage(), this); } } /** * @return key */ public string getkey() { return name; } /** * @return type */ private resourcetype gettype() { return type; } private string getname() { return name; } public set<reservation> getreservations() { return reservations; } public branchoffice getbranchoffice() { return office; } /** * @param name name set * @throws invalidrequiredstringexception */ private void setname(string name) throws invalidrequiredstringexception { if (name != null && !name.trim().isempty()) this.name = name; else throw new invalidrequiredstringexception(invalid_name, name); } private void setbranchoffice(branchoffice office) { if (office == null) { throw new illegalargumentexception(invalid_office); } else { this.office = office; } } /** * @param type type set * @throws invalidrequiredresourcetypeexception */ private void settype(resourcetype type) throws invalidrequiredresourcetypeexception { if (type == null) throw new invalidrequiredresourcetypeexception(invalid_type, type); else this.type = type; } /** * set list of reservations given list. * * @param reservations * | list want set reservations to. */ private void setreservations(set<reservation> reservations) { if (reservations != null) this.reservations = new hashset<>(reservations); else this.reservations = new hashset<>(); } /** * adds given reservation list of reservations. * * @param reservation * | reservation want add reservations. */ private void addreservation(reservation reservation) { this.reservations.add(reservation); } /** * checks if resource conflicts given resource. * * @param resource * resource want check against. * @return * true if resource conflicts given resource. */ public boolean conflictswith(resource resource) { if (gettype().hasconflictwith(resource.gettype())) return true; else return false; } /** * checks if resource if available given timespan * * @param timespan * @return true if timespans not overlap. */ public boolean isavailable(timespan timespan) { if (reservations != null && !reservations.isempty()) { (reservation reservation : reservations) if (reservation.overlapswith(timespan)) return false; // todo: checken of resource beschikbaar binnen timespan (bv. // datacenter enkel beschikbaar tussen 12:00 en 17:00 return true; } return true; } @override public int hashcode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashcode()); return result; } @override public boolean equals(object obj) { if (this == obj) return true; if (obj == null) return false; if (getclass() != obj.getclass()) return false; resource other = (resource) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } public resourcevalue getvalue() { return new resourcevalue(this); } @override public int compareto(resource o) { return this.getkey().compareto(o.getkey()); } public boolean isoftype(resourcetype other) { return gettype().equals(other); } public void reserve(reservation newreservation) throws invalidreservationexception { for(reservation reservation : getreservations()) if(reservation.conflictswith(newreservation)) throw new invalidreservationexception("reservation conflicts reservation", newreservation); addreservation(newreservation); } public boolean isofsametype(resource resource) { return isoftype(resource.gettype()); } public class resourcevalue { private final string name; private final resourcetype type; private resourcevalue(resource resource) { this.name = resource.getname(); this.type = resource.gettype(); } /** * @return name */ public string getname() { return name; } /** * @return type */ public resourcetype gettype() { return type; } } public void deletereservation(reservation reservation) { getreservations().remove(reservation); } } i've copied whole class, looks bit messy try @ bottom of class, there can find value class. picked class because it's smallest one. in example value class doesn't copy fields ones needed view.
my question is: "is there simpler way of keeping encapsulation between view , controller?"
it doesn't feel right kind of duplicate these classes
when split application in multiple layers, it's pratice use kind of "objectvalue" named "objectdto, data transfer object"[1]. there not used pure copy of model objects, can add, remove, modify fields depending on want do.
that said, can use libraries "map" entity objectvalue. instance, modelmapper http://modelmapper.org/ .
persondto persondto = mapper.map(personmodel, persondto.class);
edit : [1] according comment, valueobject & dto not same thing, if main principle remains same. imo it's question of naming convention.
dtos simple objects should not contain business logic require testing
Comments
Post a Comment