我正在开展一个学校项目,我们需要创建一个管理任务和项目的基本应用程序。我们的项目遵循 MVC 模式。重点在于应用程序的设计,我们的团队一直在努力解决某个设计问题:
封装 View 和 Controller 之间传递的数据
这意味着我们要确保 View 没有任何对真实数据的引用。我们尝试通过创建值类来解决这个问题,但这是一个巨大的解决方法。这些是最终类,本质上是普通模型类的副本。例如,如果您有一个 Project 类,那么您还将有一个名为 ProjectValue 的最终类,其所有字段与 Project 相同,只是它们是allfinal 使值类的对象不可变,因此 View 中没有任何内容可以更改。复制所有这些类来获得一些额外的封装感觉不太合适,必须有一种更简单的方法。
我将尝试用一个例子来解释这个问题:
用户想要查看所有项目。因此,他将启动应用程序并单击标有“显示项目”的按钮。该按钮将在 Controller 中启动一个名为 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()
) 并将其放入列表中,而不是普通项目中。
模型类及其内部值类的示例:
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);
}
}
我已经复制了整个类,它看起来有点乱,但尝试查看类的底部,在那里你可以找到值类。我选择这门课是因为它是最小的一门课。在此示例中,值类不会复制所有字段,而仅复制 View 所需的字段。
我的问题是:“有没有更简单的方法来保持 View 和 Controller 之间的封装?”
最佳答案
It just doesn't feel right to kind of duplicate all these classes
当您将应用程序拆分为多个层时,最好使用这种“ObjectValue”也称为“ObjectDto,数据传输对象”[1]。过去没有模型对象的纯粹副本,您可以根据您想要执行的操作添加、删除、修改字段。
也就是说,您可以使用一些库将实体“映射”到 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
关于java - View 和 Controller 之间的封装,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31428352/