在这篇博客文章中,我们将将设计较差的代码转换为一个易于使用的系统。
设计较差的代码在使用时可能会导致挫败感,并且由于其在修改,重复和高耦合方面的难度,因此可以放慢项目的进度。
我们将通过应用原则来探讨如何克服这些挑战:
- 单一责任原则
- 不要重复自己
- 脱钩
让我们卷起袖子,将不良代码转换为一个好的设计系统!
应用电子保健管理系统
我们将处理相对较小的Java控制台应用程序,因为进入一个大而复杂的应用程序将需要很多时间。它仍然足够大,可以证明现实世界中的错误和问题,并展示如何应用上述原则。
描述
e-Healthcare-Management-System是用于患者管理的控制台Java。
患者可以登录,注销,查看个人资料,查看医生,选择医生,查看约会,预约,给予反馈,查看报告,在线付款。
医生可以登录,注销,查看个人资料,查看约会,参加。
管理员可以登录,注销,查看配置文件,查看患者列表,添加/删除医生,查看医生列表,请参阅病人给出的反馈,查看报告。
。完整的描述在这里
如何阅读这篇文章
要充分理解应用程序,您应该在GitHub存储库上检查其源代码。这是您需要知道的:
-
master分支包含代码的最新版本。
-
applicationBeforeRefactor分支拥有代码的初始版本。
要了解代码如何演变,请审查两个分支。从applicationBeforeRefactor分支开始,以查看代码最初的外观,然后转到master分支。
此外,在阅读相关帖子或文章时,请定期参考源代码以更好地理解。
最初情况
注意:看看applicationBeforeRefactor分支。
i下几节我指出了代码的一些问题,然后我们看到如何通过应用SRP,干燥和去耦来解决它们。
软件包/文件夹和文件结构
当前文件夹结构在单个文件夹中具有所有类,因此很难确定哪个类属于哪个模块或组件。这可能会在编码过程中引起混乱,并使定位和修改特定类都具有挑战性。
责任
public void ShowPatientDetails(int id)/*This method all details of the patient*/
{
try {
Connection con=ConnectionProvider.getCon();
Statement st=con.createStatement();
ResultSet rs=st.executeQuery("Select * from Patients where PatientID="+id);
while(rs.next())
{
System.out.println("PatientID: "+rs.getInt(1));
System.out.println("Name: "+rs.getString(2)+" "+rs.getString(3));
System.out.println("Blood-Group: "+rs.getString(8));
System.out.println("Address: "+rs.getString(9));
System.out.println("Contact-Number: "+rs.getString(5));
System.out.print("\t********************\n");
}
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
您看到此方法出了什么问题吗?
我相信当我说它做很多事情时,我同意我的看法:创建SQL查询,从DB获取数据,映射它,然后在UI上显示(在这种情况下为UI是System.out
)。
类也是如此。其中一些示例是Main.java
,Appointment.java
... **
我们看到每种方法和班级都有压倒性的责任,因此很难维护和修改。
此外,每个方法和类的大量代码可能具有挑战性,特别是对于不是原始作者的人。
不一致
private String Doctor_Qualification;
private int docFees;
Scanner sc=new Scanner(System.in);
这是可变命名的示例,但是在项目中,我们有更多的矛盾之处。它们出现在:
中- 写评论
- 命名变量和方法
- 卷曲括号
- 凹痕
这样的不一致不仅会给生产力带来障碍,因为理解代码需要更长的时间,并且适应它变得具有挑战性,而且还助长了更大的问题。
这些不一致会给人留下深刻的印象,这可能会导致混乱。当然,在这种情况下,您可能会问自己是否没有人在意,为什么我应该?
作者喜欢打字/编码。
Connection con=ConnectionProvider.getCon();
Statement st=con.createStatement();
ResultSet rs=st.executeQuery("Select MAX(UserID) as NextUserID from Users where userType='Doctor'");
// or
st.executeUpdate("insert into Users values('"+DoctorID+"','"+"Doctor"+"','"+password+"')");
重复了几个地方和类似的代码块?
作者喜欢打字/编码,并且不知道他是重复代码,在某些地方更重要的是重复知识。
< br>
从长远来看,这种做法使代码更难维护,并在软件应用程序的不同部分重复使用。此外,增加了在重复的代码块中引入错误的风险。
让我们改变不良代码
注意:看看master分支。
从哪儿开始
注意:这不是关于重构目标的文章,就是查看如何使SRC,干燥和去耦
受益代码在一个提交中没有更改,而是逐步应用小更改。
在此步骤中,我们没有触摸代码。想法是为代码定义基本代码样式准则,例如:
-
命名
-
卷曲式样式
-
凹痕
使我们在其中执行一致性。代码样式指南在README.md文件中
第二步,我们仍然没有编码
我从大局开始。 在模块/软件包级别应用单一责任原则。基于功能和当前代码,创建了新的软件包,并重新组织了类。这个想法是每个模块/软件包都有一个责任。
下一步解耦DB访问
经过仔细检查,我们可以看到,现在我们有一些看起来像模块的东西,但是它们是高度耦合的,并且大多数方法的责任太多了。
为了解决这个问题,我们通过实现简单的,受控的更改来将DB访问从代码中删除。
public final class Repository {
private Connection connection;
private Repository() {
this.connection = ConnectionProviderDefault.getCon();
}
private Repository(Connection connection) {
this.connection = connection;
}
private static Repository repository = new Repository();
public static synchronized Repository getInstance() {
if (repository == null) {
repository = new Repository();
}
return repository;
}
@Deprecated
public Connection getConnection() {
return connection;
}
public boolean executeUpdate(String sql) {
// check source code at git
}
public <RESULT_ITEM> List<RESULT_ITEM> executeQuery(String query, Function<ResultSet, RESULT_ITEM> function) {
// check source code at git
}
}
现在,我们使用Singleton Class Repository.java
可以单点访问DB,也删除了许多重复代码。
这是用法:
Repository.getInstance().executeQuery("SELECT * FROM Appointments", mapDbRowToAppointment);
您是否注意到我是如何使用称为通过行为的技术,更多信息here
介绍业务层
将DB访问到单独的类后,UI和业务逻辑仍然高度耦合。让我们也从UI中移动业务逻辑。
这个想法是,每个模块/软件包应至少具有一个定义业务操作的接口,以及这些接口的默认实现。示例包括付款服务,反馈服务等。
这是PatientFeedback.java
的示例
public interface PatientFeedback {
PatientFeedback DEFAULT_INSTANCE = new PatientFeedbackImpl(Repository.getInstance());
List<Feedback> getFeedbacks();
boolean addFeedback(Feedback feedback);
}
请注意,裸露的接口可以访问其默认实现。
终于UI
尽管它是控制台应用程序,但UI的原理是相同的。每个模块/软件包将具有自己的类别名为Portal
的类,该类别定义其视图(遵循单个责任原则)。然后,它们都是在主要方法中组成的。
public static void main(String[] args) {
Config.getInstance().loadConfig(args == null || args.length < 1 ? null : args[0]);
Scanner sc = new Scanner(System.in);
AdminPortal adminPortal = new AdminPortal();
PatientPortal patientPortal = new PatientPortal();
DoctorPortal doctorPortal = new DoctorPortal();
PatientRegistrationPortal patientRegistrationPortal = new PatientRegistrationPortal();
我想通过创建可重复使用的数据表来减少UI组件的重新实现,特别是该应用程序中的数据表。
public final class TerminalTablePrinter {
private TerminalTablePrinter() {}
public static <ELEMENT> void printTable(List<String> header, List<ELEMENT> rows, Function<ELEMENT, List<String>> mapper) {
此外,我创建了一个登录组件LoginTerminal.java
和一个用于插入用户数据PersonTerminal.java
的组件,可以在整个代码中重复使用。这些可重复使用的UI组件放在terminalUtil
包中。
我们完了吗
代码中有很多事情应该得到改进:
- 例外处理
- 用
Statement
替换为PreparedStatement
(db访问) - db 的命名约定
- 验证层
- 外部政党自由 - 验证信用卡
应用原则:
- 单一责任原则
- 不要重复自己
- 脱钩
我们设法将设计不佳的代码转换为一个易于使用的精心设计的系统,因此我正在结束此代码转换。
但是,这是当代码精心设计时实现改进的示例。
示例用PreparedStatement
替换Statement
(DB访问)
因为用于访问数据库的代码与应用程序的其余部分是截然不同的,但没有重复,并且只有一个责任,因此很容易引入更改。
在实践中,代码的所有更改都将在Repository.java
类中发生
public boolean executeUpdate(String sql) {
this.checkConnection();
try {
PreparedStatement st= this.connection.prepareStatement(sql);
st.executeUpdate(sql);
return true;
}catch(Exception e){
e.printStackTrace();
return false;
}
finally {
try {
this.connection.close();
} catch (SQLException e) {
throw new RepositoryConnectionCloseException();
}
}
}
进行练习。可以随意实施此或其他一些改进,共享您的代码,我们可以讨论它。
最后一句话
良好的代码设计对于软件应用程序的可维护性,开发人员的生产率至关重要。
通过应用关键设计原则,例如单个责任原则,不要重复自己并将其脱钩到设计较差的代码,我们将效率低下的混乱代码转换为一个易于使用的精心设计的系统。
我们已经看到了如何创建模块,每个模块都具有单一的责任,可以与UI脱钩的业务逻辑,以及可重复使用的UI组件如何减少代码复制。按照代码样式准则以及在命名,凹痕和卷发括号中执行一致性的增量更改可以带来更好的代码质量。
通过遵循这些原则,我们可以降低引入错误的风险,使我们的代码在开发软件应用程序时更容易理解并提高生产率。