视图和控制器之间的封装
Encapsulation between view and controller
我正在做一个学校项目,我们需要创建一个管理任务和项目的基本应用程序。我们的项目遵循 MVC 模式。重点放在应用程序的设计上,我们的团队一直在努力解决某个设计问题:
封装视图和控制器之间传递的数据
这意味着我们要确保视图没有对真实数据的任何引用。我们试图通过创建 value classes 来解决此问题,但这是一个巨大的解决方法。这些是最终的 classes,它们本质上是正常模型 classes 的副本。例如,如果你有一个 class Project 那么你也会有一个最终的 class 叫做 ProjectValue字段作为 Project 除了它们都是最终的以使值 class immutable 的对象,所以在看法。复制所有这些 classes 以获得一些额外的封装感觉不对,必须有更简单的方法。
我会试着用一个例子来解释这个问题:
一位用户想要查看所有项目。因此,他将启动应用程序并单击标有 "Show projects" 的按钮。该按钮将在控制器中启动一个名为 getAll()
:
的方法
public PList<ProjectValue> getAll()
{
PList<ProjectValue> projects = PList.empty();
for (BranchOffice office : company.getBranchOffices())
{
for (Project project : office.getProjects())
{
projects = projects.plus(project.getValue());
}
}
return projects;
}
首先它遍历所有分支机构。对于分支机构中的每个项目,它都采用项目的值对象 (project.getValue()
) 并将其放在列表中而不是普通项目中。
一个模型的例子class和他的内在价值class:
public class Resource implements Serializable, Comparable<Resource> {
/**
* Variable registering the name for this resource.
*/
private String name;
private BranchOffice office;
/**
* Variable registering the resource type for this resource.
*/
private ResourceType type;
/**
* Variable registering the reservations that reserve this resource.
*/
private Set<Reservation> reservations;
/**
* Initializes the resource with a given name and type.
*
* @param name
* The name for the resource, f.e. Audi
* @param type
* The type of the 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 the key
*/
public String getKey() { return name; }
/**
* @return the type
*/
private ResourceType getType() { return type; }
private String getName() { return name; }
public Set<Reservation> getReservations() { return reservations; }
public BranchOffice getBranchOffice()
{
return office;
}
/**
* @param name the name to 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 the type to set
* @throws InvalidRequiredResourceTypeException
*/
private void setType(ResourceType type)
throws InvalidRequiredResourceTypeException
{
if (type == null)
throw new InvalidRequiredResourceTypeException(INVALID_TYPE, type);
else
this.type = type;
}
/**
* Set the list of reservations to a given list.
*
* @param reservations
* | The list you want to set the reservations to.
*/
private void setReservations(Set<Reservation> reservations)
{
if (reservations != null) this.reservations = new HashSet<>(reservations);
else this.reservations = new HashSet<>();
}
/**
* Adds a given reservation to the list of reservations.
*
* @param reservation
* | The reservation you want to add to the reservations.
*/
private void addReservation(Reservation reservation)
{
this.reservations.add(reservation);
}
/**
* Checks if this resource conflicts with a given resource.
*
* @param resource
* The resource you want to check against.
* @return
* True if this resource conflicts with the given resource.
*/
public boolean conflictsWith(Resource resource)
{
if (getType().hasConflictWith(resource.getType())) return true;
else return false;
}
/**
* Checks if a resource if available for a given timespan
*
* @param timespan
* @return True if the timespans do not overlap.
*/
public boolean isAvailable(TimeSpan timespan)
{
if (reservations != null && !reservations.isEmpty())
{
for (Reservation reservation : reservations)
if (reservation.overlapsWith(timespan))
return false;
// TODO: checken of resource beschikbaar is 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 with another 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 the name
*/
public String getName() { return name; }
/**
* @return the type
*/
public ResourceType getType() { return type; }
}
public void deleteReservation(Reservation reservation)
{
getReservations().remove(reservation);
}
}
我复制了整个class,它看起来有点乱,但试着看看class的底部,在那里你可以找到值class。我选择这个 class 因为它是最小的。在此示例中,值 class 不会复制所有字段,而只会复制视图所需的字段。
我的问题是:"Is there any simpler way of keeping encapsulation between the view and the controller?"
It just doesn't feel right to kind of duplicate all these classes
当您将应用程序拆分为多层时,最好使用这种 "ObjectValue" 也称为 "ObjectDto, Data transfer object"[1]。过去不存在模型对象的纯副本,您可以根据自己的需要添加、删除、修改字段。
也就是说,您可以使用一些库 "map" 从实体到 ObjectValue。例如,ModelMapper http://modelmapper.org/ .
PersonDto personDto = mapper.map(personModel, PersonDto.class);
编辑:
[1] 根据评论,ValueObject 和 DTO 不是一回事,即使主要原理保持不变。 IMO 这只是命名约定的问题。
DTOs are simple objects that should not contain any business logic that would require testing
我正在做一个学校项目,我们需要创建一个管理任务和项目的基本应用程序。我们的项目遵循 MVC 模式。重点放在应用程序的设计上,我们的团队一直在努力解决某个设计问题:
封装视图和控制器之间传递的数据
这意味着我们要确保视图没有对真实数据的任何引用。我们试图通过创建 value classes 来解决此问题,但这是一个巨大的解决方法。这些是最终的 classes,它们本质上是正常模型 classes 的副本。例如,如果你有一个 class Project 那么你也会有一个最终的 class 叫做 ProjectValue字段作为 Project 除了它们都是最终的以使值 class immutable 的对象,所以在看法。复制所有这些 classes 以获得一些额外的封装感觉不对,必须有更简单的方法。
我会试着用一个例子来解释这个问题:
一位用户想要查看所有项目。因此,他将启动应用程序并单击标有 "Show projects" 的按钮。该按钮将在控制器中启动一个名为 getAll()
:
public PList<ProjectValue> getAll()
{
PList<ProjectValue> projects = PList.empty();
for (BranchOffice office : company.getBranchOffices())
{
for (Project project : office.getProjects())
{
projects = projects.plus(project.getValue());
}
}
return projects;
}
首先它遍历所有分支机构。对于分支机构中的每个项目,它都采用项目的值对象 (project.getValue()
) 并将其放在列表中而不是普通项目中。
一个模型的例子class和他的内在价值class:
public class Resource implements Serializable, Comparable<Resource> {
/**
* Variable registering the name for this resource.
*/
private String name;
private BranchOffice office;
/**
* Variable registering the resource type for this resource.
*/
private ResourceType type;
/**
* Variable registering the reservations that reserve this resource.
*/
private Set<Reservation> reservations;
/**
* Initializes the resource with a given name and type.
*
* @param name
* The name for the resource, f.e. Audi
* @param type
* The type of the 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 the key
*/
public String getKey() { return name; }
/**
* @return the type
*/
private ResourceType getType() { return type; }
private String getName() { return name; }
public Set<Reservation> getReservations() { return reservations; }
public BranchOffice getBranchOffice()
{
return office;
}
/**
* @param name the name to 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 the type to set
* @throws InvalidRequiredResourceTypeException
*/
private void setType(ResourceType type)
throws InvalidRequiredResourceTypeException
{
if (type == null)
throw new InvalidRequiredResourceTypeException(INVALID_TYPE, type);
else
this.type = type;
}
/**
* Set the list of reservations to a given list.
*
* @param reservations
* | The list you want to set the reservations to.
*/
private void setReservations(Set<Reservation> reservations)
{
if (reservations != null) this.reservations = new HashSet<>(reservations);
else this.reservations = new HashSet<>();
}
/**
* Adds a given reservation to the list of reservations.
*
* @param reservation
* | The reservation you want to add to the reservations.
*/
private void addReservation(Reservation reservation)
{
this.reservations.add(reservation);
}
/**
* Checks if this resource conflicts with a given resource.
*
* @param resource
* The resource you want to check against.
* @return
* True if this resource conflicts with the given resource.
*/
public boolean conflictsWith(Resource resource)
{
if (getType().hasConflictWith(resource.getType())) return true;
else return false;
}
/**
* Checks if a resource if available for a given timespan
*
* @param timespan
* @return True if the timespans do not overlap.
*/
public boolean isAvailable(TimeSpan timespan)
{
if (reservations != null && !reservations.isEmpty())
{
for (Reservation reservation : reservations)
if (reservation.overlapsWith(timespan))
return false;
// TODO: checken of resource beschikbaar is 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 with another 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 the name
*/
public String getName() { return name; }
/**
* @return the type
*/
public ResourceType getType() { return type; }
}
public void deleteReservation(Reservation reservation)
{
getReservations().remove(reservation);
}
}
我复制了整个class,它看起来有点乱,但试着看看class的底部,在那里你可以找到值class。我选择这个 class 因为它是最小的。在此示例中,值 class 不会复制所有字段,而只会复制视图所需的字段。
我的问题是:"Is there any simpler way of keeping encapsulation between the view and the controller?"
It just doesn't feel right to kind of duplicate all these classes
当您将应用程序拆分为多层时,最好使用这种 "ObjectValue" 也称为 "ObjectDto, Data transfer object"[1]。过去不存在模型对象的纯副本,您可以根据自己的需要添加、删除、修改字段。
也就是说,您可以使用一些库 "map" 从实体到 ObjectValue。例如,ModelMapper http://modelmapper.org/ .
PersonDto personDto = mapper.map(personModel, PersonDto.class);
编辑: [1] 根据评论,ValueObject 和 DTO 不是一回事,即使主要原理保持不变。 IMO 这只是命名约定的问题。
DTOs are simple objects that should not contain any business logic that would require testing