{
  "source_file": "0930",
  "source_platform": "个人博客",
  "author": "Eugene Yan",
  "title_listed": "Design Patterns in Machine Learning Code and Systems",
  "keywords": "设计模式,ML系统,代码",
  "domain": "机器学习",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://eugeneyan.com/writing/design-patterns/",
  "publish_date": "2022-04",
  "corpus_stage": "推理部署",
  "notes": "机器学习代码与系统设计模式",
  "id": "228a1da2aa3f",
  "collected_at": "2026-10-02T14:17:06",
  "title": "Design Patterns in Machine Learning Code and Systems",
  "body_markdown": "eugeneyan\nStart Here\nWriting\nSpeaking\nPrototyping\nAbout\nDesign Patterns in Machine Learning Code and Systems\n[\nmachinelearning\nengineering\npython\n🔥\n]\n· 13 min read\nDesign patterns are not just a way to structure code. They also communicate the problem addressed and how the code or component is intended to be used.\nHere are some patterns I’ve observed in machine learning code and systems, mostly from the\nGang of Four design patterns book\n. Most developers have some familiarity with these patterns and having a basic understanding provides a shared vocabulary to discuss ideas on design and implementation.\nDesign patterns in libraries and code\nThe factory pattern decouples objects, such as training data, from how they are created.\nCreating these objects can sometimes be complex (e.g., distributed data loaders) and providing a base factory helps users by simplifying object creation and providing constraints that prevent mistakes.\nThe base factory can be defined via an interface or abstract class. Then, to create a new factory, we can subclass it and provide our own implementation details.\nPyTorch’s\nDataset\nis a good example. To create our own datasets, we should subclass\nDataset\nand override the\n__len__\nand\n__getitem__\nmethods. The former returns the size of the dataset while the latter supports indexing to get the\ni\nth example. Here’s an\nexample\nof creating a custom dataset.\nfrom\ntorch.utils.data\nimport\nDataset\nclass\nSequencesDataset\n(\nDataset\n):\ndef\n__init__\n(\nself\n,\nsequences\n:\nSequences\n,\nneg_sample_size\n=\n5\n):\nself\n.\nsequences\n=\nsequences\nself\n.\nneg_sample_size\n=\nneg_sample_size\ndef\n__len__\n(\nself\n):\nreturn\nself\n.\nsequences\n.\nn_sequences\ndef\n__getitem__\n(\nself\n,\nidx\n):\npairs\n=\nself\n.\nsequences\n.\nget_pairs\n(\nidx\n)\nneg_samples\n=\n[]\nfor\ncenter\n,\ncontext\nin\npairs\n:\nneg_samples\n.\nappend\n(\nself\n.\nsequences\n.\nget_negative_samples\n(\ncontext\n))\nreturn\npairs\n,\nneg_samples\nGensim’s\ntextcorpus\nis another example. It simplifies reading text files for downstream language modeling. Users need to override the\nget_texts()\nmethod to read and process a single line (or document) and return it as a sequence of words.\nfrom\ngensim.corpora.textcorpus\nimport\nTextCorpus\nfrom\ngensim.test.utils\nimport\ndatapath\nfrom\ngensim\nimport\nutils\nclass\nCorpusMiislita\n(\nTextCorpus\n):\nstopwords\n=\nset\n(\n'for a of the and to in on'\n.\nsplit\n())\ndef\nget_texts\n(\nself\n):\nfor\ndoc\nin\nself\n.\ngetstream\n():\nyield\n[\nword\nfor\nword\nin\nutils\n.\nto_unicode\n(\ndoc\n).\nlower\n().\nsplit\n()\nif\nword\nnot\nin\nself\n.\nstopwords\n]\ndef\n__len__\n(\nself\n):\nself\n.\nlength\n=\nsum\n(\n1\nfor\n_\nin\nself\n.\nget_texts\n())\nreturn\nself\n.\nlength\ncorpus\n=\nCorpusMiislita\n(\ndatapath\n(\n'head500.noblanks.cor.bz2'\n))\n>>>\nlen\n(\ncorpus\n)\n250\ndocument\n=\nnext\n(\niter\n(\ncorpus\n.\nget_texts\n()))\nA final example is Hugging Face’s\nDataset\nthat doesn’t require subclassing. It provides a simple way for users to load data into Apache Arrow which provides for fast lookup with low memory requirements. It also includes methods to stream, interleave, shuffle, etc.\nfrom\ndatasets\nimport\nload_dataset\nfrom\ndatasets\nimport\ninterleave_datasets\n# Load as streaming = True\nen_dataset\n=\nload_dataset\n(\n'oscar'\n,\n\"unshuffled_deduplicated_en\"\n,\nsplit\n=\n'train'\n,\nstreaming\n=\nTrue\n)\nfr_dataset\n=\nload_dataset\n(\n'oscar'\n,\n\"unshuffled_deduplicated_fr\"\n,\nsplit\n=\n'train'\n,\nstreaming\n=\nTrue\n)\n# Interleave\nmultilingual_dataset\n=\ninterleave_datasets\n([\nen_dataset\n,\nfr_dataset\n])\n# Shuffle\nshuffled_dataset\n=\nmultilingual_dataset\n.\nshuffle\n(\nseed\n=\n42\n,\nbuffer_size\n=\n10_000\n)\nThe adapter pattern increases compatibility between interfaces\n, such as data formats like CSV, Parquet, JSON, etc. This allows objects (e.g., stored data) with incompatible interfaces to collaborate. Specific to data pipelines, adapters are often used to read data stored in different formats into a standard data object such as a Dataframe.\nFor example, Pandas has almost 20 adapters to\nread\nmost file storages types into the pandas Dataframe.\nNon-exhaustive list of adapters to read different file storage formats in Pandas\nSimilarly, Spark has adapters to read from different data formats such as\nParquet\n,\nJSON\n,\nCSV\n,\nHive\n, and\nText\nfiles.\nval\nparquetDf\n=\nspark\n.\nread\n.\nparquet\n(\n\"people.parquet\"\n)\nval\njsonDf\n=\nspark\n.\nread\n.\njson\n(\n\"examples/src/main/resources/people.json\"\n)\nval\nhiveDF\n=\nsql\n(\n\"SELECT name, job_family FROM people WHERE age < 60 ORDER BY age\"\n)\nval\ncsvDf\n=\nspark\n.\nread\n.\ncsv\n(\n\"examples/src/main/resources/people.csv\"\n)\nval\ntextDf\n=\nspark\n.\nread\n.\ntext\n(\n\"examples/src/main/resources/people.txt\"\n)\nAnother example is\nApache Arrow\nwhich provides a standard columnar format across multiple data frameworks like Pandas, Spark, Parquet, Cassandra, and more. (Hugging Face’s Dataset uses Arrow for its local caching system.)\nApache Arrow standardizes in-memory columnar data for several data frameworks\nThe decorator pattern allows users to easily add functionality to their existing code.\nObjects can be “decorated” (aka adding functionality) at run time without having to update the structure or behavior of other objects of the same class.\nIn Python, decorating methods is easy via the\n@\nsyntax. Applying\n@decorator\non\nmethod()\nis equivalent to calling\nmethod = decorator(method)\n.\nA handy example of a built-in operator is functool’s\nlru_cache()\n. It saves the most recent\nx\ncalls in a dictionary which maps input arguments to returned results. Here’s an example using cache to efficiently compute Fibonacci numbers.\nfrom\nfunctools\nimport\nlru_cache\n@\nlru_cache\n(\nmaxsize\n=\nNone\n)\ndef\nfib\n(\nn\n):\nif\nn\n<\n2\n:\nreturn\nn\nreturn\nfib\n(\nn\n-\n1\n)\n+\nfib\n(\nn\n-\n2\n)\n>>>\n[\nfib\n(\nn\n)\nfor\nn\nin\nrange\n(\n16\n)]\n[\n0\n,\n1\n,\n1\n,\n2\n,\n3\n,\n5\n,\n8\n,\n13\n,\n21\n,\n34\n,\n55\n,\n89\n,\n144\n,\n233\n,\n377\n,\n610\n]\n>>>\nfib\n.\ncache_info\n()\nCacheInfo\n(\nhits\n=\n28\n,\nmisses\n=\n16\n,\nmaxsize\n=\nNone\n,\ncurrsize\n=\n16\n)\nAnother example is\nPytest’s\ndecorators to define\nfixtures\n. These fixtures can then be referenced in downstream tests. Think of fixtures as methods that generate data for testing expected behavior. These fixtures are called before running any tests and shared across tests. Here are some\nexamples\nof fixtures to load sample data and trained models.\nimport\npytest\nimport\nnumpy\nas\nnp\nfrom\nsrc.data_prep.prep_titanic\nimport\nload_df\n,\nprep_df\n,\nsplit_df\n,\nget_feats_and_labels\nfrom\nsrc.tree.decision_tree\nimport\nDecisionTree\nfrom\nsrc.tree.random_forest\nimport\nRandomForest\n# Returns data for training and evaluating our models\n@\npytest\n.\nfixture\ndef\ndummy_dataset\n():\ndf\n=\nload_df\n()\ndf\n=\nprep_df\n(\ndf\n)\ntrain\n,\ntest\n=\nsplit_df\n(\ndf\n)\nX_train\n,\ny_train\n=\nget_feats_and_labels\n(\ntrain\n)\nX_test\n,\ny_test\n=\nget_feats_and_labels\n(\ntest\n)\nreturn\nX_train\n,\ny_train\n,\nX_test\n,\ny_test\n# Returns a trained DecisionTree that is evaluated on implementation and behavior\n@\npytest\n.\nfixture\ndef\ndummy_decision_tree\n(\ndummy_dataset\n):\nX_train\n,\ny_train\n,\n_\n,\n_\n=\ndummy_dataset\ndt\n=\nDecisionTree\n(\ndepth_limit\n=\n5\n)\ndt\n.\nfit\n(\nX_train\n,\ny_train\n)\nreturn\ndt\n# Returns a trained RandomForest that is evaluated on implementation and behavior\n@\npytest\n.\nfixture\ndef\ndummy_random_forest\n(\ndummy_dataset\n):\nX_train\n,\ny_train\n,\n_\n,\n_\n=\ndummy_dataset\nrf\n=\nRandomForest\n(\nnum_trees\n=\n8\n,\ndepth_limit\n=\n5\n,\ncol_subsampling\n=\n0.8\n,\nrow_subsampling\n=\n0.8\n)\nrf\n.\nfit\n(\nX_train\n,\ny_train\n)\nreturn\nrf\nA simple decorator I often use is a\ntimer\nthat measures how long a method call takes and returns the results. This is useful when building prototypes that call different models—with varying latencies—to check the time taken for each call.\nfrom\nfunctools\nimport\nwraps\nfrom\ntime\nimport\nperf_counter\nfrom\ntyping\nimport\nCallable\nfrom\ntyping\nimport\nTuple\ndef\ntimer\n(\nfunc\n:\nCallable\n)\n->\nCallable\n:\n@\nwraps\n(\nfunc\n)\ndef\nwrapper\n(\n*\nargs\n,\n**\nkwargs\n):\nstart\n=\nperf_counter\n()\nresults\n=\nfunc\n(\n*\nargs\n,\n**\nkwargs\n)\nend\n=\nperf_counter\n()\nrun_time\n=\nend\n-\nstart\nreturn\nresults\n,\nrun_time\nreturn\nwrapper\n@\ntimer\ndef\npredict_with_time\n(\nmodel\n,\nX_test\n:\nnp\n.\narray\n)\n->\nTuple\n[\nnp\n.\narray\n]:\nreturn\nmodel\n.\npredict\n(\nX_test\n)\nThe strategy pattern lets users change the intended behavior or algorithm of an object.\nUsers can create new objects for each strategy (aka algorithm) and depending on the strategy object used, the context behavior can vary at runtime. This decouples algorithms from clients, adding flexibility and reusability to the code.\nMost machine learning libraries come with builtin algorithms and settings. Nonetheless, they also provide the flexibility for users to add their own algorithms so long as it adheres to the strategy interface.\nFor example, XGBoost provides\nvarious methods\nto construct trees (e.g., exact, approx, hist, gpu_hist) and several\nobjective functions\nsuch as squared error, logistic, and pairwise ranking. Nonetheless, we can also provide a\ncustom objective function\nif we want to, such as the squared log error example below.\nimport\nnumpy\nas\nnp\nimport\nxgboost\nas\nxgb\nfrom\ntyping\nimport\nTuple\ndef\ngradient\n(\npredt\n:\nnp\n.\nndarray\n,\ndtrain\n:\nxgb\n.\nDMatrix\n)\n->\nnp\n.\nndarray\n:\ny\n=\ndtrain\n.\nget_label\n()\nreturn\n(\nnp\n.\nlog1p\n(\npredt\n)\n-\nnp\n.\nlog1p\n(\ny\n))\n/\n(\npredt\n+\n1\n)\ndef\nhessian\n(\npredt\n:\nnp\n.\nndarray\n,\ndtrain\n:\nxgb\n.\nDMatrix\n)\n->\nnp\n.\nndarray\n:\ny\n=\ndtrain\n.\nget_label\n()\nreturn\n((\n-\nnp\n.\nlog1p\n(\npredt\n)\n+\nnp\n.\nlog1p\n(\ny\n)\n+\n1\n)\n/\nnp\n.\npower\n(\npredt\n+\n1\n,\n2\n))\ndef\nsquared_log\n(\npredt\n:\nnp\n.\nndarray\n,\ndtrain\n:\nxgb\n.\nDMatrix\n)\n->\nTuple\n[\nnp\n.\nndarray\n,\nnp\n.\nndarray\n]:\npredt\n[\npredt\n<\n-\n1\n]\n=\n-\n1\n+\n1e-6\ngrad\n=\ngradient\n(\npredt\n,\ndtrain\n)\nhess\n=\nhessian\n(\npredt\n,\ndtrain\n)\nreturn\ngrad\n,\nhess\nxgb\n.\ntrain\n({\n'tree_method'\n:\n'hist'\n,\n'seed'\n:\n1994\n},\ndtrain\n=\ndtrain\n,\nnum_boost_round\n=\n10\n,\nobj\n=\nsquared_log\n)\n# Using the custom objective function\nSimilarly, Hugging Face’s\npipeline\nmakes it easy to use various strategies (aka language models) for inference. Pipelines supports tasks such as sentiment analysis, translation, question answering, and more.\nfrom\ntransformers\nimport\npipeline\npipe\n=\npipeline\n(\n\"sentiment-analysis\"\n)\n>>>\npipe\n([\n\"This restaurant is awesome\"\n,\n\"This restaurant is aweful\"\n])\n[{\n'label'\n:\n'POSITIVE'\n,\n'score'\n:\n0.9998743534088135\n},\n{\n'label'\n:\n'NEGATIVE'\n,\n'score'\n:\n0.9996669292449951\n}]\nen_fr_translator\n=\npipeline\n(\n\"translation_en_to_fr\"\n)\n>>>\nen_fr_translator\n(\n\"How old are you?\"\n)\n[{\n'translation_text'\n:\n' quel âge êtes-vous?'\n}]\nqa_model\n=\npipeline\n(\n\"question-answering\"\n)\nquestion\n=\n\"Where do I live?\"\ncontext\n=\n\"My name is Merve and I live in İstanbul.\"\n>>>\nqa_model\n(\nquestion\n=\nquestion\n,\ncontext\n=\ncontext\n)\n{\n'answer'\n:\n'İstanbul'\n,\n'end'\n:\n39\n,\n'score'\n:\n0.953\n,\n'start'\n:\n31\n}\nThe iterator pattern provides a way to go through objects in a collection of objects.\nThis decouples the traversal algorithm from the data container and users can define their own algorithm. For example, if we have data in a tree, we might want to swap between breadth-first and depth-first traversal.\nAn example is PyTorch’s\nDataLoader\nwhich lets users define batch size, shuffling, number of workers, and even provide custom\ncollate functions\n.\nfrom\ntorch.utils.data\nimport\nDataLoader\ndataset\n=\nSequencesDataset\n(\nsequences\n)\ndef\ncollate\n(\nbatches\n):\nbatch_list\n=\n[]\nfor\nbatch\nin\nbatches\n:\npairs\n=\nnp\n.\narray\n(\nbatch\n[\n0\n])\nnegs\n=\nnp\n.\narray\n(\nbatch\n[\n1\n])\nnegs\n=\nnp\n.\nvstack\n((\npairs\n[:,\n0\n].\nrepeat\n(\nnegs\n.\nshape\n[\n1\n]),\nnegs\n.\nravel\n())).\nT\npairs_arr\n=\nnp\n.\nones\n((\npairs\n.\nshape\n[\n0\n],\npairs\n.\nshape\n[\n1\n]\n+\n1\n),\ndtype\n=\nint\n)\npairs_arr\n[:,\n:\n-\n1\n]\n=\npairs\nnegs_arr\n=\nnp\n.\nzeros\n((\nnegs\n.\nshape\n[\n0\n],\nnegs\n.\nshape\n[\n1\n]\n+\n1\n),\ndtype\n=\nint\n)\nnegs_arr\n[:,\n:\n-\n1\n]\n=\nnegs\nall_arr\n=\nnp\n.\nvstack\n((\npairs_arr\n,\nnegs_arr\n))\nbatch_list\n.\nappend\n(\nall_arr\n)\nbatch_array\n=\nnp\n.\nvstack\n(\nbatch_list\n)\n# Return item1, item2, label\nreturn\n(\ntorch\n.\nLongTensor\n(\nbatch_array\n[:,\n0\n]),\ntorch\n.\nLongTensor\n(\nbatch_array\n[:,\n1\n]),\ntorch\n.\nFloatTensor\n(\nbatch_array\n[:,\n2\n]))\n# We can customize the batch size, number of works, shuffle, etc.\ntrain_dataloader\n=\nDataLoader\n(\ndataset\n,\nbatch_size\n=\n128\n,\nshuffle\n=\nTrue\n,\nnum_workers\n=\n8\n,\ncollate_fn\n=\ncollate\n)\nThe pipeline pattern lets users chain a sequence of transformations.\nTransforms are steps to process the data, such as data cleaning, feature engineering, dimensionality reduction, etc. An estimator (aka machine learning model) can be added to the end of the pipeline. Thus, the pipeline takes data as input, transforms it, and trains a model at the end.\nIMHO, in ML pipelines, everything from data preparation to feature engineering to model hyperparameters is a parameter to tune. Being able to chain and vary transforms and estimators makes for convenient pipeline parameter tuning. How would model metrics change if we\nnp.log\ncontinuous variables or add trigrams to our text features?\nWhen using a pipeline, we should be aware that the input and output of each transform step should have a standardized format, such as a Pandas Dataframe. Also, each transform should have the required methods such as\n.fit()\nand\n.transform()\n.\nSklearn’s\nPipeline\nallows users to provide a list of transforms and a final estimator. We can also use the\nTransformerMixin\nto create\ncustom transforms\n.\nfrom\nsklearn.svm\nimport\nSVC\nfrom\nsklearn.preprocessing\nimport\nStandardScaler\nfrom\nsklearn.impute\nimport\nSimpleImputer\nfrom\nsklearn.datasets\nimport\nmake_classification\nfrom\nsklearn.model_selection\nimport\ntrain_test_split\nfrom\nsklearn.pipeline\nimport\nPipeline\nX\n,\ny\n=\nmake_classification\n(\nrandom_state\n=\n0\n)\nX_train\n,\nX_test\n,\ny_train\n,\ny_test\n=\ntrain_test_split\n(\nX\n,\ny\n,\nrandom_state\n=\n0\n)\n# Create and train pipeline\npipe\n=\nPipeline\n([(\n'imputer'\n,\nSimpleImputer\n(\nstrategy\n=\n\"median\"\n)),\n(\n'scaler'\n,\nStandardScaler\n()),\n(\n'svc'\n,\nSVC\n())])\npipe\n.\nfit\n(\nX_train\n,\ny_train\n)\n# Evaluate the pipeline\n>>>\npipe\n.\nscore\n(\nX_test\n,\ny_test\n)\n0.88\nSpark’s MLlib also uses the concept of\npipelines\nwhich are built around dataframes, transformers, and estimators.\nimport\norg.apache.spark.ml.\n{\nPipeline\n,\nPipelineModel\n}\nimport\norg.apache.spark.ml.classification.LogisticRegression\nimport\norg.apache.spark.ml.feature.\n{\nHashingTF\n,\nTokenizer\n}\nimport\norg.apache.spark.ml.linalg.Vector\nimport\norg.apache.spark.sql.Row\n// Prepare training documents from a list of (id, text, label) tuples.\nval\ntraining\n=\nspark\n.\ncreateDataFrame\n(\nSeq\n(\n(\n0L\n,\n\"a b c d e spark\"\n,\n1.0\n),\n(\n1L\n,\n\"b d\"\n,\n0.0\n),\n(\n2L\n,\n\"spark f g h\"\n,\n1.0\n),\n(\n3L\n,\n\"hadoop mapreduce\"\n,\n0.0\n)\n)).\ntoDF\n(\n\"id\"\n,\n\"text\"\n,\n\"label\"\n)\n// Configure an ML pipeline, which consists of three stages: tokenizer, hashingTF, and lr.\nval\ntokenizer\n=\nnew\nTokenizer\n()\n.\nsetInputCol\n(\n\"text\"\n)\n.\nsetOutputCol\n(\n\"words\"\n)\nval\nhashingTF\n=\nnew\nHashingTF\n()\n.\nsetNumFeatures\n(\n1000\n)\n.\nsetInputCol\n(\ntokenizer\n.\ngetOutputCol\n)\n.\nsetOutputCol\n(\n\"features\"\n)\nval\nlr\n=\nnew\nLogisticRegression\n()\n.\nsetMaxIter\n(\n10\n)\n.\nsetRegParam\n(\n0.001\n)\nval\npipeline\n=\nnew\nPipeline\n()\n.\nsetStages\n(\nArray\n(\ntokenizer\n,\nhashingTF\n,\nlr\n))\n// Fit the pipeline to training documents.\nval\nmodel\n=\npipeline\n.\nfit\n(\ntraining\n)\n// Now we can optionally save the fitted pipeline to disk\nmodel\n.\nwrite\n.\noverwrite\n().\nsave\n(\n\"/tmp/spark-logistic-regression-model\"\n)\nDesign patterns in systems\nBeyond being used in code and libraries, I think design patterns also apply to systems. Here are two patterns commonly seen in machine learning systems.\nThe proxy pattern allows us to provide a substitute for the production database or service.\nThe proxy can then perform tasks on behalf of the endpoint.\nOne example is using a cache proxy to serve search queries. Search queries follow the Pareto principle where a fraction of queries account for the bulk of requests. Thus, by caching results for the top queries (e.g., query suggestion, query classification, retrieval and ranking), we can reduce the amount of compute required for real-time inference.\nIn the image below, when a request is made, our service first checks if the result is available in the cache. If so, it returns the result from the cache; if not, the query is passed through for real-time inference.\nUsing a cache proxy to serve common requests and reduce compute cost from real-time inference\nAnother example is using a reverse proxy to serve models. One way to scale is by serving via multiple machines (i.e., horizontal scaling). Nonetheless, we still want to provide a single address for access. A reverse proxy can accept incoming requests via a single address and distribute them across multiple servers to spread the load and ensure low latency.\nUsing a reverse proxy to distribute incoming requests to multiple backend servers\nWhat’s the difference between a load balancer and a reverse proxy?\nWhen we refer to a load balancer we are referring to a very specific thing - a server that balances inbound requests across two or more web servers to spread the load.\nA reverse proxy, however, typically has any number of features:\nLoad balancing: As discussed above\nCaching: It can cache content from the web server(s) behind it and thereby reduce the load on the web server(s) and return some static content back to the requester without having to get the data from the web server(s)\nSecurity: It can protect the web server(s) by preventing direct access from the internet; it might do this through simple means by just obfuscating the web server(s) or it may have some more active components that actually review inbound requests looking for malicious code\nSSL acceleration: When SSL is used; it may serve as a termination point for those SSL sessions so that the workload of dealing with the encryption is offloaded from the web server(s)\nSource\nThe mediator pattern provides a middleman that restricts direct communication between services\n, forcing services to collaborate—indirectly—via the mediator. This promotes loose coupling by preventing services from referring to one another explicitly and implementing to the other services’ interface.\nIf there are mediators in our existing systems, we should expect to serve machine learning services through them instead of directly to downstream applications (e.g., e-commerce sites, trade orders). Mediators typically have standard requirements (e.g., request schemas) and constraints (e.g., latency, rate limiting) to adhere to.\nFor example, we might have multiple recommendation widgets to display on the home page of Netflix. Instead of allowing widgets to decide how they’re shown to users, we can require each widget to go through a mediator which ranks the widgets. The mediator may also perform checks (e.g., exclude widgets with less than\nx\nitems) or update widgets (e.g., deduplicate items across multiple widgets).\nUsing a mediator to allocate widgets to placements on the home page\n• • •\nKnowing that something is a factory, decorator, or mediator helps us use them as intended. Conversely, using the right pattern when developing libraries or systems helps users understand our intent. What other common design patterns have you seen in machine learning code and systems? Please\nreach out\nor comment below!\nReferences\nDesign Patterns: Elements of Reusable Object-Oriented Software\nHead First Design Patterns\nRefactor Guru Design Patterns\nml-design patterns (Mark Saroufim)\nIf you found this useful, please cite this write-up as:\nYan, Ziyou. (Jun 2022). Design Patterns in Machine Learning Code and Systems. eugeneyan.com.\n        https://eugeneyan.com/writing/design-patterns/.\nor\n@article{yan2022patterns,\n  title   = {Design Patterns in Machine Learning Code and Systems},\n  author  = {Yan, Ziyou},\n  journal = {eugeneyan.com},\n  year    = {2022},\n  month   = {Jun},\n  url     = {https://eugeneyan.com/writing/design-patterns/}\n}\nShare on:\nBrowse related tags:\n[\nmachinelearning\nengineering\npython\n🔥\n]\nor\nSearch\n« What I Wish I Knew About Onboarding Effectively\nWhy You Should Write Weekly 15-5s »\nJoin\n11,800+\nreaders getting updates on machine learning, RecSys, LLMs, and engineering.\nGet email updates",
  "metadata": {
    "title": "Design Patterns in Machine Learning Code and Systems",
    "description": "Understanding and spotting patterns to use code and components as intended.",
    "keywords": null,
    "author": "Eugene Yan",
    "og:site_name": "eugeneyan.com",
    "og:title": "Design Patterns in Machine Learning Code and Systems",
    "og:type": "article",
    "og:description": "Understanding and spotting patterns to use code and components as intended.",
    "og:url": "https://eugeneyan.com/writing/design-patterns/",
    "og:image": "https://eugeneyan.com/assets/og_image/design-patterns.jpg",
    "twitter:card": "summary_large_image",
    "twitter:domain": "eugeneyan.com",
    "twitter:title": "Design Patterns in Machine Learning Code and Systems",
    "twitter:description": "Understanding and spotting patterns to use code and components as intended.",
    "twitter:image": "https://eugeneyan.com/assets/og_image/design-patterns.jpg",
    "article:published_time": "2022-06-12T00:00:00+00:00",
    "article:author": "https://eugeneyan.com/about/",
    "article:section": "posts",
    "article:tag": "🔥"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/228a1da2aa3f/code/body-000.txt",
      "chars": 538,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-001.txt",
      "chars": 608,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-002.txt",
      "chars": 466,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-003.txt",
      "chars": 349,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-004.txt",
      "chars": 306,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-005.txt",
      "chars": 1104,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-006.txt",
      "chars": 494,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-007.txt",
      "chars": 779,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-008.txt",
      "chars": 632,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-009.txt",
      "chars": 985,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-010.txt",
      "chars": 640,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-011.txt",
      "chars": 1154,
      "language": "text"
    },
    {
      "file": "attachments/228a1da2aa3f/code/body-012.txt",
      "chars": 250,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/icon-search.svg",
      "bytes": 425,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-feeb4bd87b8704a903b2.svg",
      "src": "https://eugeneyan.com/assets/icon-search.svg",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/pandas_read.webp",
      "bytes": 35264,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-fd862a6e26f6f1082315.webp",
      "src": "https://eugeneyan.com/assets/pandas_read.webp",
      "alt": "Non-exhaustive list of adapters to read different file storage formats in Pandas",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/arrow.webp",
      "bytes": 10070,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-78b0071267ca80928e76.webp",
      "src": "https://eugeneyan.com/assets/arrow.webp",
      "alt": "Apache Arrow standardizes in-memory columnar data for several data frameworks",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/proxy.webp",
      "bytes": 14014,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-71c3653df51fd5f85fd9.webp",
      "src": "https://eugeneyan.com/assets/proxy.webp",
      "alt": "Using a cache proxy to serve common requests and reduce compute cost from real-time inference",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/load-balancer.gif",
      "bytes": 1139741,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-f6d3d8414486dcfa2ae9.gif",
      "src": "https://eugeneyan.com/assets/load-balancer.gif",
      "alt": "Using a reverse proxy to distribute incoming requests to multiple backend servers",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/mediator.webp",
      "bytes": 41282,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-b1c1154319faa2720c9c.webp",
      "src": "https://eugeneyan.com/assets/mediator.webp",
      "alt": "Using a mediator to allocate widgets to placements on the home page",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/icon-twitter.svg",
      "bytes": 892,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-0083a7581b07e5f40e7b.svg",
      "src": "https://eugeneyan.com/assets/icon-twitter.svg",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/icon-linkedin.svg",
      "bytes": 559,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-f7a16c3e46e5838cec4d.svg",
      "src": "https://eugeneyan.com/assets/icon-linkedin.svg",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/bluesky.svg",
      "bytes": 745,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-be754e8ecdaf6de94f55.svg",
      "src": "https://eugeneyan.com/assets/bluesky.svg",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/icon-facebook.svg",
      "bytes": 1123,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-5d55b59733923cf14223.svg",
      "src": "https://eugeneyan.com/assets/icon-facebook.svg",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://eugeneyan.com/assets/icon-mail.svg",
      "bytes": 841,
      "reused": false,
      "local_path": "attachments/228a1da2aa3f/images/body-b179147617228dfafb9f.svg",
      "src": "https://eugeneyan.com/assets/icon-mail.svg",
      "alt": "",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://eugeneyan.com/",
      "label": "eugeneyan",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/start-here/",
      "label": "Start Here",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/writing/",
      "label": "Writing",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/speaking/",
      "label": "Speaking",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/prototyping/",
      "label": "Prototyping",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/about/",
      "label": "About",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/search/",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/tag/machinelearning/",
      "label": "machinelearning",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/tag/engineering/",
      "label": "engineering",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/tag/python/",
      "label": "python",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/tag/🔥/",
      "label": "🔥",
      "type": "a"
    },
    {
      "href": "https://en.wikipedia.org/wiki/Design_Patterns",
      "label": "Gang of Four design patterns book",
      "type": "a"
    },
    {
      "href": "https://pytorch.org/docs/stable/data.html",
      "label": "Dataset",
      "type": "a"
    },
    {
      "href": "https://github.com/eugeneyan/recsys-nlp-graph/blob/master/src/ml/data_loader.py",
      "label": "example",
      "type": "a"
    },
    {
      "href": "https://radimrehurek.com/gensim/corpora/textcorpus.html",
      "label": "textcorpus",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/docs/datasets/loading",
      "label": "Dataset",
      "type": "a"
    },
    {
      "href": "https://pandas.pydata.org/docs/user_guide/io.html",
      "label": "read",
      "type": "a"
    },
    {
      "href": "https://spark.apache.org/docs/latest/sql-data-sources-parquet.html",
      "label": "Parquet",
      "type": "a"
    },
    {
      "href": "https://spark.apache.org/docs/latest/sql-data-sources-json.html",
      "label": "JSON",
      "type": "a"
    },
    {
      "href": "https://spark.apache.org/docs/latest/sql-data-sources-csv.html",
      "label": "CSV",
      "type": "a"
    },
    {
      "href": "https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html",
      "label": "Hive",
      "type": "a"
    },
    {
      "href": "https://spark.apache.org/docs/latest/sql-data-sources-text.html",
      "label": "Text",
      "type": "a"
    },
    {
      "href": "https://arrow.apache.org/overview/",
      "label": "Apache Arrow",
      "type": "a"
    },
    {
      "href": "https://docs.python.org/3/library/functools.html",
      "label": "lru_cache()",
      "type": "a"
    },
    {
      "href": "https://docs.pytest.org/en/7.1.x/index.html",
      "label": "Pytest’s",
      "type": "a"
    },
    {
      "href": "https://docs.pytest.org/en/6.2.x/fixture.html",
      "label": "fixtures",
      "type": "a"
    },
    {
      "href": "https://github.com/eugeneyan/testing-ml/blob/master/tests/tree/fixtures.py",
      "label": "examples",
      "type": "a"
    },
    {
      "href": "https://github.com/eugeneyan/testing-ml/blob/master/src/utils/timer.py",
      "label": "timer",
      "type": "a"
    },
    {
      "href": "https://xgboost.readthedocs.io/en/stable/treemethod.html",
      "label": "various methods",
      "type": "a"
    },
    {
      "href": "https://xgboost.readthedocs.io/en/stable/parameter.html",
      "label": "objective functions",
      "type": "a"
    },
    {
      "href": "https://xgboost.readthedocs.io/en/stable/tutorials/custom_metric_obj.html",
      "label": "custom objective function",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/docs/transformers/main_classes/pipelines",
      "label": "pipeline",
      "type": "a"
    },
    {
      "href": "https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html",
      "label": "Pipeline",
      "type": "a"
    },
    {
      "href": "https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html",
      "label": "TransformerMixin",
      "type": "a"
    },
    {
      "href": "https://www.section.io/engineering-education/custom-transformer/",
      "label": "custom transforms",
      "type": "a"
    },
    {
      "href": "https://spark.apache.org/docs/latest/ml-pipeline.html",
      "label": "pipelines",
      "type": "a"
    },
    {
      "href": "https://serverfault.com/questions/127021/what-is-the-difference-between-load-balancer-and-reverse-proxy",
      "label": "Source",
      "type": "a"
    },
    {
      "href": "https://twitter.com/eugeneyan",
      "label": "reach out",
      "type": "a"
    },
    {
      "href": "https://www.goodreads.com/book/show/58128.Head_First_Design_Patterns",
      "label": "Head First Design Patterns",
      "type": "a"
    },
    {
      "href": "https://refactoring.guru/design-patterns",
      "label": "Refactor Guru Design Patterns",
      "type": "a"
    },
    {
      "href": "https://github.com/msaroufim/ml-design-patterns",
      "label": "ml-design patterns (Mark Saroufim)",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/writing/onboarding/",
      "label": "« What I Wish I Knew About Onboarding Effectively",
      "type": "a"
    },
    {
      "href": "https://eugeneyan.com/writing/15-5/",
      "label": "Why You Should Write Weekly 15-5s »",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:52:22Z",
  "source_url": "https://eugeneyan.com/writing/design-patterns/",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 19806,
  "expected_body_images": 11,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/228a1da2aa3f/pages/source.html",
  "local_html": "attachments/228a1da2aa3f/pages/article.html",
  "body_base_path": "attachments/228a1da2aa3f/pages/",
  "body_markdown_path": "attachments/228a1da2aa3f/pages/article.md"
}