สวัสดี! เอกสารนี้มีคำแนะนำส่วนตัวของฉันสำหรับนโยบายขั้นตอนและมาตรฐานเกี่ยวกับวิธีการพัฒนาที่ควรดำเนินการ
รหัสโครงการควรจัดโครงสร้างด้วยการผสมผสานของ โฟลเดอร์โดยคุณสมบัติ และ โฟลเดอร์ต่อประเภท
ความหมายในระดับรากที่คุณมีโฟลเดอร์กำหนดคุณสมบัติ ตัวอย่างเช่น:
|-- common/
|-- authentication/
|-- settings/
คุณลักษณะทั่วไป/หลักประกอบด้วยคลาสฟังก์ชั่นหรือวิดเจ็ตที่ควรมีอยู่ในคุณสมบัติใด ๆ
ดังนั้นแนวคิดคือการมีคุณสมบัติของแอปพลิเคชันของคุณหารด้วยโฟลเดอร์ระดับบนสุด
คิดว่าทุกโฟลเดอร์ระดับบนสุดเป็นแพ็คเกจ Dart/Flutter แยกต่างหากการพึ่งพาหรือขอบเขตการเข้าถึงของแอปพลิเคชันของคุณ
ดังนั้นหากคุณเพิ่มบางสิ่งบางอย่างลงในโฟลเดอร์ทั่วไปคุณต้องถามตัวเองว่า 99.% ของแอปพลิเคชันของเราต้องการสิ่งนั้นหรือไม่?
หากคุณคิดว่ามันเป็นการพึ่งพาการพึ่งพาร่วมกันมักจะประกาศใน pubspec.yaml ของคุณเสมอ
ดังนั้นก่อนที่จะเพิ่มคลาสฟังก์ชั่นหรือวิดเจ็ตไปยังโฟลเดอร์ทั่วไปให้ถามตัวเองว่า:
ภายในแต่ละโฟลเดอร์คุณสมบัติคุณมีโฟลเดอร์ที่กำหนดตามประเภท
นี่คือประเภทโฟลเดอร์ที่จะเป็นส่วนหนึ่งของโฟลเดอร์คุณสมบัติ
|-- ui/
|-- cubits/
|-- domain_objects/
|-- exceptions/
|-- use_cases
|-- services/
|-- repositories/
|-- data_sources/
|-- dtos/
หากมีไฟล์มากกว่าหนึ่งไฟล์ที่เกี่ยวข้องกับไฟล์เดียวคุณสามารถจัดกลุ่มไว้ในโฟลเดอร์เช่นรหัสที่สร้างขึ้นสำหรับคลาสในไฟล์ ตัวอย่างเช่น:
|-- authentication/
|---- cubits/
|------ login/
|-------- login_cubit.dart
|-------- login_cubit.freezed.dart
|-------- login_cubit.g.dart
รหัสที่เกี่ยวข้องกับอินเทอร์เฟซอุปกรณ์ของผู้ใช้ตัวอย่างเช่น: UI PageBuilders, หน้าจอ UI, มุมมอง UI, ส่วนประกอบ UI
หน้าจอเป็นส่วนประกอบส่วนต่อประสานผู้ใช้ที่เติมเต็มการแสดงอุปกรณ์ทั้งหมดและเป็นคอนเทนเนอร์ของมุมมองส่วนต่อประสานผู้ใช้หรือส่วนประกอบ (ปุ่ม, ช่องทำเครื่องหมาย, รูปภาพและ ECT)
นอกจากนี้หน้าจอเป็นปลายทางการนำทางแอปพลิเคชันเฉพาะ
แนวทาง:
class LoginScreen extends StatelessWidget {
//...fields and constructor...
@override
Widget build ( BuildContext context) {
return Column (
children : < Widget > [
_LoginLogo ,
_LoginForm ,
_LoginActions ,
_LoginFooter
],
);
}
}
// extracted by use case.
class _LoginLogo extends StatelessWidget {}
// extracted by update area.
class _LoginForm extends StatefullWidget {}
// extracted by update area.
class _LoginActions extends StatelesssWidget {}
// extracted by update area.
class _LoginFooter extends StatelessWidget {} login_screen.dart
part 'login_screen_components.dart' ;
class LoginScreen extends StatelessWidget {
//...fields and constructor...
@override
Widget build ( BuildContext context) {
return Column (
children : < Widget > [
_LoginLogo ,
_LoginForm ,
_LoginActions ,
_LoginFooter
],
);
}
}
// Is kept here because it's does not break the 400 max line rule.
class _LoginLogo extends StatelessWidget {} login_screen_components.dart
part of 'login_screen.dart' ;
/// [LoginScreen] 's fields.
class _LoginForm extends StatelessWidget {}
// ************************ Footer ************************
/// [LoginScreen] 's footer.
class _LoginFooter extends StatelessWidget {}
// ************************* ACTIONS *********************************
/// [LoginScreen] 's actions.
class _LoginActions extends StatelessWidget {} class LoginScreen extends StatelessWidget {
@override
Widget build ( BuildContext context) {
return BlocBuilder < LoginCubit , LoginCubitState >(
// Here cubit is not specified either.
builder : ( BuildContext context, LoginCubitState state) {},
);
}
} class SomeWidget extends StatelessWidget {
@override
Widget build ( BuildContext context) {
//...
}
}
class _SubTree1 extends StatelessWidget {}
class _SubTree2 extends StatelessWidget {}
class _SubTree3 extends StatelessWidget {}
class _SubTree4 extends StatelessWidget {} class LoginScreen extends StatelessWidget {
@override
Widget build ( BuildContext context) {
return Column (
childreen : < Widget > [
Button1 (onClick : _openRegisterUser),
Button2 (onClick : _openLogin),
Field (onTextChanged : _onUserNameTextChanged),
],
);
}
void _openRegisterUser () {}
void _openLogin () {}
void _onUserNameTextChanged ( String newText) {}
}มุมมองคือส่วนประกอบส่วนต่อประสานผู้ใช้ที่ไม่เติมเต็มการแสดงอุปกรณ์ทั้งหมดและเป็นคอนเทนเนอร์ของมุมมองหรือส่วนประกอบของผู้ใช้ (ปุ่ม, ช่องทำเครื่องหมาย, รูปภาพและ ECT)
นอกจากนี้มุมมองไม่ใช่ปลายทางการนำทางของแอปพลิเคชัน
แนวทาง:
ศอกเป็นคลาสที่มีตรรกะทางธุรกิจสำหรับ UI ของคุณศอกถูกผูกไว้กับรัฐซึ่งเป็นตัวแทนของสถานะ UI
คำแนะนำแนวทาง:
Future < String ?> bookAppointment ( BookingData booking);
Future < bool > login ( String username, String password);ชั้นรัฐศอกเป็นตัวแทนของสถานะของ UI ที่ล้อมรอบเวลาที่กำหนด
คำแนะนำแนวทาง:
class LoginState {
bool logginSuceeded // avoid this.
}ชั้นเรียนที่ใช้สำหรับการดำเนินการธุรกิจเดียว/เป้าหมาย/งาน/งานในโดเมนของคุณ
แนวทาง:
class GetCurrentUserUseCase , class SignInUseCase ;UseCase ท้าย เสมอcall() เสมอซึ่งประเภทการส่งคืนเป็นผลมาจากการดำเนินการกรณีการใช้งานRepositories Services หรือ high level coordinators อื่น ๆ เท่านั้นDataSource หรือ Cubit หรือโต้ตอบกับ DTOS หรือวัตถุระดับต่ำอื่น ๆCubit หรือใน UseCases อื่น ๆตัวอย่าง:
class GetCurrentUserUseCase {
final UserRepository _repository;
const GetCurrentUserUseCase ( this ._repository);
Future < User ?> call () async {
await _repository. getCurrentUser ();
}
} class AuthenticateMemberUseCase {
/// Create a [AuthenticateMemberUseCase] .
const AuthenticateMemberUseCase (
/* constructor arguments */
);
/* ... fields ...*/
/// Execute the use case.
Future < TegTask < void >> call ( MemberAuthenticationCredentials credentials) {
return runTaskSafelyAsync < void >(() async {
final bool isEmailValid = credentials.email.isEmail;
if ( ! isEmailValid) {
throw const TegEmailInvalidException ();
}
final bool isConnected = await _hostDeviceInternetService. isConnected ();
if ( ! isConnected) {
throw const TegInternetUnavailableException ();
}
final String ? deviceId = await _hostDeviceInfoRepository. getDeviceId ();
if (deviceId == null ) {
throw const TegDeviceIdUnavailableException ();
}
final PublicPrivateKeyPair keyPair = await _keyGenerator. generate ();
final Member member = await _authService. signIn (
email : credentials.email,
password : credentials.password,
deviceId : deviceId,
publicKey : keyPair.publicKey,
);
await _updateCurrentMemberUseCase (
member : member,
memberPrivateKey : keyPair.privateKey,
deviceId : deviceId,
);
await _saveMemberAuthenticationCredentialsUseCase (credentials);
final TegTask < List < Account >> memberAccountsTask = await _getMemberAccountsUseCase ();
if (memberAccountsTask.failed) {
throw memberAccountsTask.exception;
}
await _updateMemberCurrentAccountUseCase (
member : member,
deviceId : deviceId,
memberPrivateKey : keyPair.privateKey,
account : memberAccountsTask.result.first,
);
});
}
}คลาสที่ให้การเข้าถึงข้อมูลโดยใช้ เฉพาะ การดำเนินการ CRUD สำหรับคุณสมบัติเฉพาะหรือขอบเขตของคุณสมบัติเช่น:
abstract class BookingRepository {
Futute < Booking > getBookingById ( String id);
Future < List < Booking >> getBookings ();
Future < void > deleteBookingById ( String id);
Future < void > saveBooking ( Booking booking);
}คำแนะนำแนวทาง:
class DefaultBookingRepository implements BookingRepository {
final LocalBookingDataSource local;
final RemoteBookingDataSource remote;
Future < Booking ?> getBookingById ( String id) async {
Booking ? savedBooking = await local. getBookingById (id);
if (savedBooking == null ) {
Booking ? remoteBooking = await remote. getBookingById (id);
if (remoteBooking != null ) {
await local. saveBooking (remoteBooking);
}
return remoteBooking;
}
return savedBooking;
}
//...other operations...
}ส่วนต่อประสาน
abstract class BookingRepository {}การดำเนินการ
class DefaultBookingRepository implements BookingRepository {}
class AnonymousBookingRepository implements BookingRepository {}
class PremiumBookingRepository implements BookingRepository {}คลาสที่ให้การเข้าถึงฟังก์ชันสำหรับคุณสมบัติเฉพาะหรือขอบเขตของคุณสมบัติเช่น:
abstract class AuthenticationService {
Future < void > authenticate ( String username, String password);
//...other functionalities...
}
abstract class AppointmentService {
Future < void > register ( Appointment appointment);
//...other functionalities...
}คำแนะนำแนวทาง
คลาสที่ใช้การเข้าถึงข้อมูลที่อยู่ในพื้นที่หรือจากระยะไกล ตัวอย่าง
abstract class LocalBookingDataSource {
Futture < void > saveBooking ( Booking booking);
//...other functionalities...
}
abstract class RemoteBookingDataSource {
Future <booking> saveBooking ();
}คำแนะนำแนวทาง:
class SQLiteBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}
class RestApiBookingDataSource implements RemoteBookingDataSource {
//...............Implementation................
}
class MemoryBookingDataSource implements LocalBookingDataSource {
//...............Implementation................
}วัตถุการถ่ายโอนข้อมูล - ใช้เพื่อถ่ายโอนข้อมูลจากระบบหนึ่งไปยังอีกระบบหนึ่ง ตัวอย่าง
class ApiRequest {
//...fields...
}
class ApiResponse {
//...fields...
} class SQLiteTableDefinition {
//...fields...
}แนวทาง:
class ApiRequest {
//...fields...
factory ApiRequest . fromDO ( DOObject doObject) {
//...mapper code...
}
DOObject toDO () {
//...mapper code...
}
}วัตถุโดเมนคือการแสดงคลาสข้อมูลของส่วนหนึ่งของธุรกิจหรือรายการภายในพวกเขาจะใช้เพื่อดำเนินการตรรกะทางธุรกิจโดยไม่ขึ้นกับแพลตฟอร์มไลบรารีหรือเครื่องมือ
วัตถุโดเมนอาจเป็นตัวแทนตัวอย่างเช่น บุคคลสถานที่กิจกรรมกระบวนการทางธุรกิจหรือแนวคิด และ ใบแจ้งหนี้ผลิตภัณฑ์การทำธุรกรรมหรือรายละเอียดของบุคคล
แนวทาง:
เช่นเดียวกับโครงสร้างรหัสโครงการ
ก่อนอื่นชื่อคุณสมบัติจากนั้นโฟลเดอร์ assets ที่มีไฟล์
ตัวอย่าง:
|-- authentication/
|---- assets/
|------ password_hidden_icon.svg
|------ forget_password_icon.svg
|------ background.png