ระบบ Refactoring validator
ฉันมีระบบตรวจสอบความถูกต้องที่ตรวจสอบข้อมูลอินพุตก่อนบันทึกลงในฐานข้อมูล สมมติว่าฉันต้องการสร้างผู้ใช้ใหม่ เราอยู่ในระดับบริการ:
package main.user;
import main.entity.User;
import main.user.validator.attributesvalidators.UserAttributesValidator;
import main.user.validator.availabilityvalidators.UserAvailabilityValidator;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserCrudActivitiesService {
private final UserRepository userRepository;
public UserCrudActivitiesService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<String> createUser(User user) {
UserAttributesValidator userAttributesValidator = new UserAttributesValidator();
UserAvailabilityValidator userAvailabilityValidator = new UserAvailabilityValidator(userRepository);
List<String> messages = userAttributesValidator.validate(user);
messages.addAll(userAvailabilityValidator.check(user));
if (messages.isEmpty()) {
userRepository.save(user);
//TODO passwordencoder
}
return messages;
}
public User updateUser(User user) {
return userRepository.save(user);
}
}
เรามีตัวตรวจสอบความถูกต้องสองตัวโดยอันดับแรกจะตรวจสอบว่าคุณสมบัติของผู้ใช้นั้นใช้ได้หรือไม่จากนั้นแอตทริบิวต์บางอย่างนั้นฟรีหรือไม่ดังนั้นเราจึงมั่นใจว่าผู้ใช้รายนี้จะไม่ซ้ำใคร
โครงสร้าง Validators:
ในทั้งสองเรามีอินเทอร์เฟซเดียวกัน: (ตัวอย่างเช่นอินเทอร์เฟซสำหรับแอตทริบิวต์)
package main.user.validator.attributesvalidators;
import main.entity.User;
public interface IUserAttributesValidator {
String validate(User user);
}
จากนั้นเราก็ได้สิ่งที่เรียกว่า (อีกครั้งสำหรับแอตทริบิวต์) UserAttributesValidatorเป็นคลาสที่มีตัวตรวจสอบความถูกต้องอื่น ๆ ทั้งหมดและภายในตัวสร้างเราจะสร้างรายการตัวตรวจสอบทั้งหมดเพื่อให้เราสามารถวนซ้ำทั้งหมดในสตรีมเดียว
package main.user.validator.attributesvalidators;
import main.entity.User;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class UserAttributesValidator {
final private List<IUserAttributesValidator> validators;
public UserAttributesValidator() {
validators = new ArrayList<>();
validators.add(new UserEmailValidator());
validators.add(new UserFirstNameValidator());
validators.add(new UserLastNameValidator());
validators.add(new UserPasswordValidator());
validators.add(new UserPhoneValidator());
validators.add(new UsernameValidator());
}
public List<String> validate(User user) {
return validators.stream()
.map(e -> e.validate(user))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
เราได้รับรายชื่อเป็นผลลัพธ์และไม่เป็นไร สิ่งเดียวกันที่ทำสำหรับAvailabilityValidator
หนึ่งในโปรแกรมตรวจสอบความถูกต้องเช่น:
package main.user.validator.attributesvalidators;
import main.entity.User;
public class UsernameValidator implements IUserAttributesValidator {
public static final int NAME_MAXIMUM_LENGTH = 30;
public static final int NAME_MINIMUM_LENGTH = 3;
public static final String NAME_ILLEGAL_CHARACTER_REGEX = "[A-Za-z0-9]+";
@Override
public String validate(User user) {
String attribute = user.getUsername();
if (attribute.length() > NAME_MAXIMUM_LENGTH) {
return "username is too long";
} else if (attribute.length() < NAME_MINIMUM_LENGTH) {
return "username is too short";
} else if (!attribute.matches(NAME_ILLEGAL_CHARACTER_REGEX)) {
return "username contains illegal character";
}
return null;
}
}
ตอนนี้ความกังวลของฉันคือ .... ฉันคิดว่านี่เป็นการออกแบบที่ไม่ดี ฉันหมายถึงฉันสร้างอินเทอร์เฟซ IUserAttributesValidator จากนั้นฉันสร้างคลาสที่มีตัวตรวจสอบความถูกต้องอื่น ๆ ทั้งหมดที่มีเมธอดซึ่งเป็นชื่อเดียวกับเมธอดในอินเทอร์เฟซ ฉันสงสัยว่าฉันสามารถรวมสองสิ่งนี้เป็นหนึ่งเดียวได้หรือไม่? เป็นไปได้ไหม? มีความเป็นไปได้ที่จะปรับปรุงรหัสนี้หรือไม่? อีกความคิดของฉันคือทั้ง (ตัวตรวจสอบความพร้อมใช้งานและแอตทริบิวต์) มีอินเทอร์เฟซเดียวกันกับวิธีการเดียวกัน แต่ต่างกัน แต่ฉันไม่รู้ว่าเป็นไปได้หรือไม่ที่จะมีอินเทอร์เฟซเดียวสำหรับทั้งสอง
คำตอบ
ดังที่ฉันได้กล่าวไว้ในความคิดเห็นของฉันด้านล่างคำถามของคุณ java ครอบคลุมความต้องการของคุณในการกำหนดตัวตรวจสอบความถูกต้องที่กำหนดเองสำหรับข้อมูลของคุณก่อนบันทึกลงในฐานข้อมูล แพ็กเกจที่อยู่คู่กับspringเฟรมเวิร์กคือjavax.validationแพ็กเกจดังนั้นให้ยกตัวอย่างโค้ดตรวจสอบความถูกต้องที่คุณกำหนดเอง:
public class UsernameValidator implements IUserAttributesValidator {
public static final int NAME_MAXIMUM_LENGTH = 30;
public static final int NAME_MINIMUM_LENGTH = 3;
public static final String NAME_ILLEGAL_CHARACTER_REGEX = "[A-Za-z0-9]+";
@Override
public String validate(User user) {
String attribute = user.getUsername();
if (attribute.length() > NAME_MAXIMUM_LENGTH) {
return "username is too long";
} else if (attribute.length() < NAME_MINIMUM_LENGTH) {
return "username is too short";
} else if (!attribute.matches(NAME_ILLEGAL_CHARACTER_REGEX)) {
return "username contains illegal character";
}
return null;
}
}
สิ่งนี้สามารถแทนที่ได้โดยใช้กลไกของคำอธิบายประกอบโดยตรงในUserชั้นเรียนของคุณด้วยวิธีนี้:
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class User {
@Size(min=3, max=30, message="username length should be between 3 and 30 chars")
//it seems me username should contain just these chars
@Pattern(regexp="[A-Za-z0-9]+", message="username contains illegal characters")
private String username;
}
กลไกเดียวกันนี้สามารถนำไปใช้กับสาขาอื่น ๆ ในUserชั้นเรียนของคุณได้เมื่อคุณต้องการในลักษณะเดียวกัน
มองข้ามความเป็นไปได้ในการนำเครื่องมือนี้ไปใช้กับ Spring framework ... สิ่งที่คุณมีนั้นค่อนข้างใกล้เคียงกับการเป็นรูปแบบการออกแบบคอมโพสิตดังนั้นจึงเป็นการออกแบบที่รู้จักและยอมรับกันดี สิ่งที่คุณต้องเปลี่ยนเพื่อให้บรรลุคือการให้ทั้งตัวตรวจสอบความถูกต้องแต่ละตัวและตัวตรวจสอบความถูกต้องผสมใช้อินเทอร์เฟซเดียวกัน มันสมเหตุสมผลที่จะมีการIUserAttributesValidatorส่งคืน a List<String>หรือCollection<String>ในตัวอย่างของคุณUsernameValidatorอาจพบการละเมิดมากกว่าหนึ่งรายการในอินพุตและจะเป็นการสะดวกที่จะส่งคืนการละเมิดทั้งความยาวและตัวอักษรจากการเรียกใช้เดียวกัน (แม้ว่าจะตรวจสอบความยาวและชุดอักขระเหมือนกัน ตัวตรวจสอบมีกลิ่นเหมือนการละเมิดความรับผิดชอบเพียงครั้งเดียว)
นอกจากนี้แทนที่จะส่งคืนรายการคุณสามารถส่งรายการเป็นพารามิเตอร์เพื่อให้ผู้ตรวจสอบแต่ละรายสามารถผนวกข้อผิดพลาดของตนเข้ากับรายการที่มีอยู่แทนที่จะสร้างรายการใหม่ทิ้งในทุกข้อผิดพลาด
iterface Validator<T> {
void validate(T target, List<String> errors);
}