Table of Contents
מדוע הטמעת מסגרות צריכה להיות מוגברת
קידוד הוא דאגה חוצה-זירה נוגעת בכל שכבת מערכת תוכנה.ביישומים של הפקת סקאלה, החזרה של הכניסה משתנה לעתים קרובות עם הזמן: פרויקט עשוי להתחיל עם קונסולה במהלך פיתוח, לעבור להחלפת קבצים מתגלגלת ב staging, ובסופו של דבר לשלב עם שירות ריכוזי הדבקה כמו Logstash או Splunk בייצור. ללא מסגרת נשגת extensible , אלה מעברים כדי לשנות את האסטרטגיה של זמן קוד הליבה.
דפוס שיטת ה-FLT:0 (FLT:1) מתייחס לבעיה זו על ידי הפרדת ממשק הכניסה מהיישום הבטון.הפרדה זו תואמת את עקרון Open/Closed Principle: המערכת נותרה פתוחה להרחבה (הגרמים החדשים ניתן להוסיף) אך סגורה לשינוי (קוד לקוח אינו צריך לשנות).
הבנת שיטת המפעל
דפוס שיטת המפעל הוא דפוס עיצוב בריא המגדיר ממשק ליצירת אובייקט, אך מאציל את החלטת ההרגעה לכיתות.בניגוד ל- Simple Factory idiom (שמשתמשים בשיטה סטטית אחת עם היגיון מותני), דפוס החרושת האמיתי מבוסס על ירושה או פולימורפיזם מבוסס תכונה כדי לאפשר תת-מעמדות לקבוע איזו שיעור קונקרטי למתן מיידיות.
דפוס זה הוא בעל ערך במיוחד כאשר מסגרת אינה יכולה לצפות את הסוגים המדויקים של אובייקטים שהיא חייבת ליצור מראש. בהקשר של כניסה, המסגרת יודעת שהיא צריכה לוגר, אבל הסוג הספציפי של הלוגר (console, קובץ, רשת, מסד נתונים) נקבע בריצה על בסיס תצורה, משתנים סביבה, או פריסה ההקשר.
משתתפים מרכזיים בתבנית
- (ב) ,0) ייצור (התכונה ה"נמוכה" (Logger Model): Defines the Interface for Objects the Factory Method.
- (ב) ,0) ייצור (ConsoleLogger, FileLogger וכו '): אנדרט 1 מיישם את ממשק המוצר.
- (הופנה מהדף "0Creator" (LoggerFactory): "הההסברה" (Declaring the Factory Method) אשר מחזיר אובייקט מוצר.
- (ב) ⁇ :0 (ConcreteCreator (אופציונלי): 1 מעלים את שיטת המפעל להחזיר מקרים מסוימים של קונקרטט.
עיצוב ה-Logger Trait Hierarchy
הבסיס של כל מסגרת כניסה אינטנסיבית הוא ממשק ממושך היטב.ב Scala, תכונות לספק מנגנון טבעי להגדרת החוזה הזה. ממשק כניסה מינימלי צריך לחשוף שיטות לרמות יומן נפוצות תוך השאר גנרי מספיק כדי לתמוך בתוספות מגוונות.
trait Logger {
def debug(message: => String): Unit
def info(message: => String): Unit
def warn(message: => String): Unit
def error(message: => String, cause: Option[Throwable] = None): Unit
}
באמצעות פרמטרים בשם (ראה LT:1) הוא בחירה עיצובית מכוונת: זה מפריך הערכה הודעה עד הלוגר מחליט אם ההודעה צריכה להיפלט למעשה.עבור נתיבי קוד קריטיים בביצועים שבהם debug logging הוא מוגבל, זה נמנע מהעלות של חסימה לחלוטין.
המונחים: Log Leveling
שיפור מעשי הוא להטמיע רמת יומן המסנן ישירות לתוך התכונה.זה מונע הודעות debug של הפועלוז להגיע לתפוקה כאשר רק אזהרות או שגיאות נדרשים.
sealed trait LogLevel
case object Debug extends LogLevel
case object Info extends LogLevel
case object Warn extends LogLevel
case object Error extends LogLevel
trait Logger {
protected val level: LogLevel
def debug(message: => String): Unit = log(Debug, message)
def info(message: => String): Unit = log(Info, message)
def warn(message: => String): Unit = log(Warn, message)
def error(message: => String, cause: Option[Throwable] = None): Unit =
log(Error, message, cause)
protected def log(level: LogLevel, message: => String, cause: Option[Throwable] = None): Unit
}
עיצוב זה נותן לכל ספקול בולט שליטה על סף משלו, תוך שמירה על הממשק הציבורי עקבי.גר הקונסולה עשוי להדפיס הכל, בעוד שגליצר קובץ ייצור עשוי לדכא הודעות debug אלא אם כן נקבע במפורש אחרת.
המונחים: Concrete Loggers
עם ההיררכיה של התכונה במקום, יישום יומני בטון הופך פשוט.כל גרף מבסס את מנגנון הפלט שלו ומכבד את המסנן מבוסס רמה תורשתית מן התכונה הבסיס.
הקונסולה Logger
class ConsoleLogger(override val level: LogLevel = Debug) extends Logger {
override protected def log(
level: LogLevel,
message: => String,
cause: Option[Throwable] = None
): Unit = {
val timestamp = java.time.Instant.now
println(s"[$timestamp] [$level] $message")
cause.foreach { t =>
t.printStackTrace(System.out)
}
}
}
הקונסולה לוגר אידיאלי לפיתוח ולפענוח.הוא מתיישר מיד לסטנדרט, מה שהופך את זה קל לצפות בזרימת יומן בזמן אמת.הוספת דגימות וטביעות ערימת עוזר במהלך פתרון בעיות מבלי לדרוש כל כלי חיצוני.
תגית: Logger
class FileLogger(
filePath: String,
override val level: LogLevel = Info,
append: Boolean = true
) extends Logger {
import java.io.{BufferedWriter, FileWriter}
private val writer = new BufferedWriter(new FileWriter(filePath, append))
override protected def log(
level: LogLevel,
message: => String,
cause: Option[Throwable] = None
): Unit = {
val timestamp = java.time.Instant.now
val entry = s"[$timestamp] [$level] $message${cause.fold("")(t => s"\n${t.getStackTrace.mkString("\n")}")}\n"
writer.write(entry)
writer.flush()
}
def close(): Unit = writer.close()
}
FileLogger כותב לנתיב מוגדר ותומכת בסף רמת הגלות הניתנת להגדרה.השיטה (FLT:5) חשובה לניהול משאבים: יש לשחרר את הטיפול בקובץ כראוי, במיוחד ביישומים ארוכי טווח.
רשת Logger (UDP דוגמא)
אחת החוזקות של דפוס שיטת החרושת היא כי הוספת סוגי הגלוג החדשים לעתים נדירות דורש שינוי קוד קיים.אגר רשת שולח רשומות יומני על UDP לאספן מרכזי מראה את יכולת ההתעלות:
class UdpLogger(
host: String,
port: Int,
override val level: LogLevel = Warn
) extends Logger {
import java.net.{DatagramPacket, DatagramSocket, InetAddress}
private val socket = new DatagramSocket()
private val address = InetAddress.getByName(host)
override protected def log(
level: LogLevel,
message: => String,
cause: Option[Throwable] = None
): Unit = {
val payload = s"[$level] $message".getBytes("UTF-8")
val packet = new DatagramPacket(payload, payload.length, address, port)
socket.send(packet)
}
}
הגרף שולח חבילות UDP למארח מרוחק, כי קוד הלקוח תלוי רק בתכונה (FLT:8), מעבר מקובץ לוגר ל- UdpLogger דורש לא יותר מאשר שינוי התצורה שמניעה את המפעל.
בניית המפעל
המפעל מערער את ההיגיון לבחירתו וממהיר את הלוגר המתאים.ב Scala, אובייקט מלווה עם שיטת שימוש הוא אידיוטי ומספק מס נקי ללקוחות.
מפעל ניהול-Driven Factory
object LoggerFactory {
sealed trait Config
object Config {
final case class Console(level: LogLevel = Debug) extends Config
final case class File(path: String, level: LogLevel = Info, append: Boolean = true) extends Config
final case class Udp(host: String, port: Int, level: LogLevel = Warn) extends Config
}
def apply(config: Config): Logger = config match {
case Config.Console(level) =>
new ConsoleLogger(level)
case Config.File(path, level, append) =>
new FileLogger(path, level, append)
case Config.Udp(host, port, level) =>
new UdpLogger(host, port, level)
}
}
דפוס זה משתמש בכיתות מקרה חתומות כדי לייצג תצורה של גרגר.ההיררכיה החתומה מבטיחה דפוס ממצה תואם בזמן ההקמה: הוספת סוג חדש של גאגר דורש הוספת מעמד מקרה חדש ומקרה חדש בהבעה המשחק.ההמפיץ מזהיר אם מקרה חסר, אשר מקטין שגיאות ריצה.
מפעל מבוסס סביבה
בפריסה רבות, תצורה של מיקום נקבעת על ידי משתנים סביבתיים ולא על תצורה ברמת קוד. מפעל שקורא משתנים סביבתיים יכול לפשט את הפריסה על פני סביבות שונות:
object LoggerFactory {
def fromEnvironment(): Logger = {
val loggerType = sys.env.getOrElse("LOGGER_TYPE", "console").toLowerCase
val level = sys.env.get("LOG_LEVEL").map(parseLevel).getOrElse(Info)
loggerType match {
case "console" => new ConsoleLogger(level)
case "file" =>
val path = sys.env.getOrElse("LOG_FILE", "application.log")
new FileLogger(path, level)
case "udp" =>
val host = sys.env.getOrElse("LOG_HOST", "localhost")
val port = sys.env.get("LOG_PORT").map(_.toInt).getOrElse(514)
new UdpLogger(host, port, level)
case other =>
System.err.println(s"Unknown logger type: $other, falling back to console")
new ConsoleLogger(level)
}
}
private def parseLevel(s: String): LogLevel = s.toLowerCase match {
case "debug" => Debug
case "info" => Info
case "warn" => Warn
case "error" => Error
case _ => Info
}
}
גישה זו מועילה במיוחד בסביבות מקוטבות שבהן משתנים הסביבה הם המנגנון הראשי של התצורה.המפעל הופך לנקודה אחת של שינוי עבור תצורה של כניסה בכל השירותים.
שימוש ב- Logging Framework
קוד הלקוח אינטראקציה בלעדית עם התכונה (FLT:11 ), זה decoupling אומר כי שאר היישום אין תלות במשרה מצטברת על כל יישום ביוגר קונקרטי.
המונחים:
val logger: Logger = LoggerFactory(LoggerFactory.Config.Console(Debug))
logger.debug("Entering method computeResults")
logger.info("Processing completed successfully")
logger.warn("Disk space below threshold")
logger.error("Connection refused", Some(new RuntimeException("timeout")))
כניסה לתוך כיתות
עבור יישומים גדולים יותר, הזרקת הלוגר באמצעות פרמטרים של בניית מבנים שומרת על העיצוב נקי ומבחן:
class DataService(logger: Logger, database: Database) {
def fetchUser(id: String): Option[User] = {
logger.debug(s"Fetching user with id: $id")
val result = database.queryUser(id)
result match {
case Some(user) =>
logger.info(s"Found user: ${user.name}")
Some(user)
case None =>
logger.warn(s"User not found: $id")
None
}
}
}
בתבנית זו, אין ל-FLT:14 ידע אם הכניסה הולכת לקונסולה, קובץ או מעל הרשת.המפעל יוצר את הגרף המתאים בנקודת הכניסה של היישום וחוט אותו לתוך היררכיה השירות.
בדיקה עם תבנית המפעל
אחד היתרונות המעשיים של עיצוב זה הוא מבחן יכולת. כי המפעל יוצר גרפים המבוססים על תצורה, מבחן יכול להזריק לוגר מיוחד שלוכד את הפלט של הנפקת הנפקת הנפקת עילה.
class TestLogger extends Logger {
val messages: scala.collection.mutable.ListBuffer[(LogLevel, String)] =
scala.collection.mutable.ListBuffer.empty
override val level: LogLevel = Debug
override protected def log(
level: LogLevel,
message: => String,
cause: Option[Throwable] = None
): Unit = {
messages += ((level, message))
}
}
// In tests:
val testLogger = new TestLogger()
val service = new DataService(testLogger, mockDatabase)
service.fetchUser("42")
assert(testLogger.messages.exists {
case (Info, msg) if msg.contains("Found user") => true
case _ => false
})
דפוס זה מבטל את הצורך בלעג מסגרות עבור חששות כניסה.ה-FLT:16 ליישם את אותה תכונה כמו יומני ייצור, כך אימות ההתנהגות הוא סוג בטוח ופשוט.
השוואה עם גישות חלופיות
דפוס שיטת החרושת אינו הדרך היחידה להשיג כניסה בלתי צפויה ב Scala.הבנת ההסכמים עם גישות אחרות מסייעת להבהיר מדוע שיטת המפעל היא לעתים קרובות הבחירה הנכונה עבור מערכות ייצור.
מפעל פשוט Idiom
פרויקטים רבים של סקאלה מתחילים עם פשוט (FLT:17) מחזירה לוגר מבוסס על פרמטר מיתר, בעוד פשוט יותר ליישם, גישה זו אינה בקנה מידה טוב: כל סוג חדש של גרגר דורש שינוי תפקוד המפעל, והלוגיקה המרכזית יכולה להפוך לצוואר בקבוק תחזוקה.
המונחים: growth tubes
מסגרות כמו Guice או MacWire יכולות לצריף גאדג'טים באופן אוטומטי.עם זאת, הם מציגים מורכבות נוספת ולנהל את זמן ההיתר כי לעתים קרובות מיותר עבור דאגה חוצה-טווח כמו כניסה.תבנית החרושת מספקת גמישות דומה מבלי לדרוש מסגרת DI.
שילובים של Logger
גישה פונקציונלית טהורה עשויה לייצג את הלוגרים כפונקציות כמו פונקציות (FLT:18 ).זה עובד טוב בספריות כמו חתולים אפקט אבל מוסיף תלות בסוגי השפעה שעשויים להיות לא מתאימים לפרויקטים שכבר אינם משתמשים במערכות אפקט פונקציונלי.
דפוס שיטת המפעל תופס קרקע בינונית פרגמטית: הוא בנוי יותר מאשר מפעל פשוט תנאי, אבל פחות פולשני מאשר מסגרת DI מלאה או מערכת השפעה פונקציונלית.
הרחבות מתקדמות
ברגע שתשתית המפעל הבסיסית נמצאת במקום, ניתן להוסיף כמה תכונות מתקדמות עם שינויים מינימליים בקוד.
אתר Logger
לוגגר מורכב מכמה גאגרנים בו זמנית.זה שימושי עבור תרחישים שבו אותו הודעת יומן חייב להיות כתוב הן קובץ והן לוח בקרה ניטור:
class CompositeLogger(loggers: Seq[Logger], override val level: LogLevel = Debug) extends Logger {
override protected def log(
level: LogLevel,
message: => String,
cause: Option[Throwable] = None
): Unit = {
loggers.foreach(_.log(level, message, cause))
}
}
המפעל יכול ליצור יומני מורכב על ידי קבלת רצף של תצורה.קוד הלקוחות עדיין רואה מקרה אחד (FLT:20).
המונחים:
חסימת I/O בגרגורים יכולה לזלזל בביצועי היישום. An Async logger עוטפת גרף קיים ונציגים כותב לבריכת חוט ייעודי:
class AsyncLogger(underlying: Logger, executor: scala.concurrent.ExecutionContext) extends Logger {
override val level: LogLevel = underlying.level
override protected def log(
level: LogLevel,
message: => String,
cause: Option[Throwable] = None
): Unit = {
val msg = message // evaluate now, before async boundary
executor.execute(() => underlying.log(level, msg, cause))
}
}
החתלתול הזה מיישם את אותה תכונה של FLT:22 כך שניתן להכניס אותה באופן שקוף על ידי המפעל ללא שינויים בקוד הלקוחות.
ההליכים הטובים ביותר והמלכודות הנפוצות
בניית מסגרת כניסה בלתי צפויה עם דפוס שיטת המפעל היא פשוטה, אבל כמה שיטות לשפר את התוצאה של מערכות ייצור.
המונחים: Sealed Type Hierarchies for Configuration
באמצעות תכונות חתומות או שיעורי מקרה עבור תצורה של גרגר מבטיח כי ההתאמה דפוס במפעל הוא ממצה.זה משמר שגיאות משעות ריצה כדי להרכיב זמן, אשר מפחית הפתעות בייצור.
ניהול משאבים באופן משמעותי
(הדברים) הם אלה שחולקים את ה[[המאה ה-20]], [[המאה ה-20]], [[1924]], [[1924]]]], [[1924]]]], [[1924]]]]]], [[1924]]]]]], [[1924]]]]]]]]]], [[1924]]]]]]]], [[1924]]]]]]]]
להימנע אופטימיזציה מוקדמת
מסגרות כניסה רבות אופטימיזציה עבור דרך ערכת כתיבה או באמצעות מבני נתונים ללא מנעול.התחל עם יישום פשוט ואופטימיזציה רק לאחר פרופיל מגלה כי הכניסה היא צוואר בקבוק.המודל של המפעל הופך את זה קל להחליף לוגר איטי עבור אחד מהר יותר מאוחר.
שמור על Trait Minimal
עמיד בפיתוי להוסיף שיטות נוחות לתכונה של FLT:26.ממשק מינימלי קל יותר ליישם ולבדוק.תבנית ספציפית דומיין או סינון לוגיקה ניתן להוסיף כמו שיטות הרחבה או גליונים עוטפים.
משאבים חיצוניים ללמידה עמוקה יותר
- (ב) [ה]ב"ה]"ה'[דרוש מקור]" (ב[[1924]]]], [[1924]]]]]]]]]]]], [[1924]]]]]]]]]]]]]]]]]]]]]]]]]] [[1924]]]]]]]]]]]]]]]]
- (ב) [15] ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
- (ב) ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
מסקנה
דפוס שיטת המפעל ב Scala מספק בסיס נקי, נרחב לבניית מסגרות כניסה שמתאימות לדרישות משתנות.על ידי בהתאם לתכונה ולא למעמד קונקרטי, קוד היישום נשאר מחוספס מן הפרטים של פלט יומן, מה שמאפשר להוסיף סוגים חדשים של גאגר, לשנות יעדים, ולהציג אופטימיזציה ביצועים ללא לוגיקה קיימת.
התבניות מפרויקטים קטנים עם קונסולה אחת למערכות מבוזרות גדולות שעושות שימוש בערכי כניסה דרך ערוצים מרובים בו-זמנית. בשילוב עם ההיררכיה החתומה של סקאלה ופרמטרים של שם-שם, דפוס שיטת המפעל מספק גמישות ובטיחות במידה שווה.