浅谈目前我开发的前端项目用到的设计模式

浅谈目前我开发的前端项目用到的设计模式

前言

设计模式很多,看到一个需求,项目,我们去开发的时候,肯定是做一个整体的设计进行开发,而在这次我项目中,我也做了一个整体的设计,为什么要设计,一个是我们要跟安卓进行交互,一个是参与的人员也是比较多,三四个前端的参与进来,如果不做整体的设计的规划,每个人都有自己的命名习惯,而且一些重复的的业务代码,进行了重复的造轮子使用,所以,就会用到了一些设计模式,规避这些重复的问题。其次也是为了跟安卓形成一个规范,然后按文档进行开发,它那边定义好方法,我这边按照规范进行使用,我这边定义好函数 ,它那边按照规范进行调用,避免很多的一个调试过程。本文将分享我在实际开发中使用的一些设计模式,并结合代码示例进行说明。

为什么要用设计模式?

设计模式是软件开发中被反复使用的、经过验证的、可复用的解决方案。它们通常用于解决常见的设计问题和代码结构问题,帮助开发人员以更高效、更可维护的方式编写代码。设计模式的主要作用包括:

  1. 提高代码可读性和可维护性

    • 通过使用设计模式,代码结构变得更加清晰和规范,其他开发人员可以更容易理解和维护代码。
  2. 促进代码重用

    • 提供了标准化的解决方案,这些解决方案可以在不同项目中重复使用,从而减少重复劳动。
  3. 提高开发效率

    • 设计模式提供了一套现成的解决方案,开发人员可以直接应用这些模式,而无需从头开始设计,从而加快开发速度。
  4. 改善代码的灵活性和可扩展性

    • 通过使用设计模式,代码可以更容易地进行修改和扩展,适应不断变化的需求。
  5. 提供通用的设计词汇

    • 设计模式为开发人员提供了一套通用的词汇,使他们可以更有效地交流和讨论设计问题和解决方案。

说到设计模式,大家想到的就是六大原则,23种模式。这么多模式,并非都要记住,但作为前端开发,对于前端出现率高的设计模式还是有必要了解并掌握的

那么,我们先了解六大原则

六大原则:

  • 依赖倒置原则(Dependence Inversion Principle):高层(业务层)不应该直接调用底层(基础层)模块
  • 开闭原则(Open Close Principle):单模块对拓展开放、对修改关闭
  • 单一原则(Single Responsibility Principle):单模块负责的职责必须是单一的
  • 迪米特法则(Law of Demeter):对外暴露接口应该简单
  • 接口隔离原则(Interface Segregation Principle):单个接口(类)都应该按业务隔离开
  • 里氏替换原则(Liskov Substitution Principle):子类可以替换父类

六大原则也可以用六个字替换:高内聚低耦合。

  • 层不直接依赖底层:依赖倒置原则
  • 部修改关闭,外部开放扩展:开闭原则
  • 合单一功能:单一原则
  • 知识接口,对外接口简单:迪米特法则
  • 合多个接口,不如隔离拆分:接口隔离原则
  • 并复用,子类可以替换父类:里氏替换原则

我们采用模式编写时,要尽可能遵守这六大原则

23 种设计模式分为“创建型”、“行为型”和“结构型”

  1. 创建型模式

    • 这些模式主要关注对象的创建过程,优化对象创建的灵活性和重用性。
    • 例子:单例模式(Singleton)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)、建造者模式(Builder)、原型模式(Prototype)。
  2. 结构型模式

    • 这些模式主要关注对象和类的组合,优化代码的结构和组织。
    • 例子:适配器模式(Adapter)、装饰器模式(Decorator)、代理模式(Proxy)、外观模式(Facade)、桥接模式(Bridge)、组合模式(Composite)、享元模式(Flyweight)。
  3. 行为型模式

    • 这些模式主要关注对象和类之间的交互和职责分配,优化算法和业务逻辑的实现。

    • 例子:观察者模式(Observer)、策略模式(Strategy)、命令模式(Command)、责任链模式(Chain of Responsibility)、状态模式(State)、模板方法模式(Template Method)、迭代器模式(Iterator)、访问者模式(Visitor)、中介者模式(Mediator)、备忘录模式(Memento)、解释器模式(Interpreter)。

在这里插入图片描述

我项目中用到的设计模式

太多了,根本记不住,我也记不住,就说一下我在项目用用到了什么设计模式,我也是看一些优秀的代码,结合我的项目需求,进行使用。

代码片段1

一段代码,用到了四种设计模式,请看,解决什么问题呢,实现了一个桥接模块,使得前端可以方便地与原生 Android 代码进行异步通信,并且支持事件的订阅和发布。

interface Callback {resolve: (value: unknown) => void;reject: (reason?: any) => void;
}type EventListener = (...args: any[]) => void;declare global {interface Window {AndroidHome?: {showString: (message: string) => void;showToast:(message: string) => void;showUserLoginCamera:()=> void;getBoardInfo:()=> Promise<unknown> ;showFaceCheckDialog:()=>void;};onNativeCallback?: (callbackId: string, result: any, error?: any) => void;onNativeEvent?: (eventName: string, ...args: any[]) => void;show?: (a: any, b: any) => void;}
}const JSBridge = (function () {const callbackMap: Map<string, Callback> = new Map();const eventListeners: Map<string, Set<EventListener>> = new Map();let callbackCounter = 0;function callNative(method: string, params: any): Promise<unknown> {return new Promise((resolve, reject) => {const callbackId = `cb_${callbackCounter++}`;callbackMap.set(callbackId, { resolve, reject });const isAndroid = Boolean(window.AndroidHome);const message = { method, params, callbackId };try {if (isAndroid) {window.AndroidHome?.showString(JSON.stringify(message));} else {console.warn("Unsupported environment");return reject(new Error("Unsupported environment"));}} catch (error) {reject(error);}});}function onNativeCallback(callbackId: string, result: any, error?: any): void {const callback = callbackMap.get(callbackId);if (callback) {error ? callback.reject(error) : callback.resolve(result);callbackMap.delete(callbackId);}}function isJsonString(str: string) {try {JSON.parse(str);} catch (e) {return false;}return true;}function ensureJsonObject(input: string | null) {if (typeof input === 'string') {if (isJsonString(input)) {return JSON.parse(input);} else {try {return JSON.parse(JSON.stringify(input));} catch (e) {console.error("Conversion to JSON failed:", e);return null; // 或者根据需要返回其他值}}} else if (typeof input === 'object' && input !== null) {return input;} else {console.error("Input is neither a valid JSON string nor an object");return null; // 或者根据需要返回其他值}}function onNativeEvent(eventName: string, ...args: any[]): void {console.log(eventName, ...args);const firstArg = args[0]; //取第一个参数,进行转化const listeners = eventListeners.get(eventName);if (listeners) {listeners.forEach((listener) => listener(ensureJsonObject(firstArg)));}}function addEventListener(eventName: string, listener: EventListener){if (!eventListeners.has(eventName)) {eventListeners.set(eventName, new Set());}eventListeners.get(eventName)?.add(listener);return listener}function removeEventListener(eventName: string, listener: EventListener): void {eventListeners.get(eventName)?.delete(listener);}function removeAllEventListeners(eventName: string): void {eventListeners.delete(eventName);}window.onNativeCallback = onNativeCallback;window.onNativeEvent = onNativeEvent;return {callNative,addEventListener,removeEventListener,removeAllEventListeners,};
})();export default JSBridge;

页面使用以react为例子

   const androidData = (data: InteractionItem)=>{handlerData() //数据进行更新}//监听以及移出监听useEffect(()=>{JSBridge.addEventListener('campusActivityEvent',androidData);return(()=>{JSBridge.removeEventListener('campusActivityEvent',androidData)})},[]) //前端调用测试,window.onNativeEvent('campusActivityEvent', { method:'more', params:{test:'参数',web:''},timeStamp:''});

安卓调用

 webView.evaluateJavascript("window.onNativeEvent('" + eventName + "', " + eventData + ");", null);

这样前端就可以进行,注册监听对应的事件,监听安卓调用这个函数,传过来的事件,参数。从而达到通信效果,

1.模块模式:

描述: 通过立即调用的函数表达式(IIFE)创建一个封装的模块,使内部变量和函数成为私有的,并返回一个包含公共接口的对象。

在代码中体现:

const JSBridge = (function () {// 私有变量和函数const callbackMap = new Map();const eventListeners = new Map();let callbackCounter = 0;// 公共函数function callNative(method, params) {// ...}function onNativeCallback(callbackId, result, error) {// ...}function onNativeEvent(eventName, ...args) {// ...}return {callNative,addEventListener,removeEventListener,removeAllEventListeners,};
})();
2.单例模式:

描述: 确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

在代码中体现:

JSBridge 模块本身就是一个单例,通过 IIFE 的方式确保 JSBridge 只会被初始化一次,并且可以被全局访问。

3.观察者模式:

**描述:**定义对象间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。

在代码中体现:

事件监听器的管理和事件的发布通知机制体现了观察者模式。

const eventListeners = new Map();function onNativeEvent(eventName, ...args) {console.log(eventName, ...args);const firstArg = args[0];const listeners = eventListeners.get(eventName);if (listeners) {listeners.forEach((listener) => listener(ensureJsonObject(firstArg)));}
}function addEventListener(eventName, listener) {if (!eventListeners.has(eventName)) {eventListeners.set(eventName, new Set());}eventListeners.get(eventName)?.add(listener);return listener;
}function removeEventListener(eventName, listener) {eventListeners.get(eventName)?.delete(listener);
}function removeAllEventListeners(eventName) {eventListeners.delete(eventName);
}
4.发布-订阅模式:

描述:这是观察者模式的一种具体实现,发布者和订阅者通过一个消息代理进行通信,而不需要显式地相互引用。

在代码中体现eventListeners 充当了消息代理的角色,管理事件的订阅和发布。

例如,管理事件监听器的添加和删除,以及事件的发布通知。

对于发布-订阅,我这边也实现了一个完整的事件类型

export class EventEmitter {events: any;constructor() {this.events = {};}// 订阅事件subscribe(eventName: string, callback: Function) {if (!this.events[eventName]) {this.events[eventName] = [];}this.events[eventName].push(callback);return () => this.unsubscribe(eventName, callback);}// 发布事件publish(eventName: string, data: any) {const eventCallbacks = this.events[eventName];if (eventCallbacks) {eventCallbacks.forEach((callback: Function) => callback(data));}}// 取消订阅unsubscribe(eventName: string, callback: Function) {if (this.events[eventName]) {this.events[eventName] = this.events[eventName].filter((cb: Function) => cb !== callback);}}// 只订阅一次once(eventName: string, callback: Function) {const onceWrapper = (...args) => {callback(...args);this.unsubscribe(eventName, onceWrapper);};return this.subscribe(eventName, onceWrapper);}
}
// // 使用示例
// const eventEmitter = new EventEmitter();
// // 订阅事件
// const unsubscribe = eventEmitter.subscribe('userLoggedIn', (data: any) => {
//     console.log('User logged in:', data);
// });
// // 发布事件
// eventEmitter.publish('userLoggedIn', { id: 1, name: 'John Doe' });// // 取消订阅
// unsubscribe();// // 只订阅一次
// eventEmitter.once('oneTimeEvent', (data: any) => {
//     console.log('This will only be called once:', data);
// });// eventEmitter.publish('oneTimeEvent', { message: 'Hello' });
// eventEmitter.publish('oneTimeEvent', { message: 'This wont be logged' });

代码片段2

这里定义了CardStaticDataSet 类,用于管理和操作组件的组静态数据。包括默认的静态数据,跟样式数据。当然,这是我得部分业务代码,有缺失的,并不完整,只是拿出来说明。

export class CardStaticDataSet {private staticDataList: Partial<CardStaticData>[] = [];constructor(params: Partial<CardStaticData>[]) {this.staticDataList = params}// 返回指定组件静态数据 returnStaticDataOfTheSpecifiedComponent = <T extends any>(key: SortModuleNameList): Partial<CardStaticDataDetail> & T => {try {if (this.staticDataList.map(o => o.moduleName).includes(key)) {let result = this.staticDataList.find(o => o.moduleName == key)?.cardDatareturn result as any} else {throw Error(key + '不存在对象中:' + JSON.stringify(this.staticDataList))}} catch (error) {return {} as any}};// 返回指定组件的样式信息returnTheStyleInformationOfTheSpecifiedComponent = (key: SortModuleNameList, componentRatio: number = 1): Partial<CuttingComponentStyleInformation> => {try {if (this.staticDataList.map(o => o.moduleName).includes(key)) {let result = this.staticDataList.find(o => o.moduleName == key)?.cardDataresult = Object.assign({}, result)delete result?.cardStaticDatareturn Object.assign({}, cuttingComponentRatio(result.cardSize[componentRatio].size), result.style)} else {throw Error(key + '不存在对象中:' + JSON.stringify(this.staticDataList))}} catch (error) {return {} as any}};// 返回组件类型以及名称returnComponentTypeAndName = (): CardFunctionInformation[] => {try {let functionList = this.staticDataList.map(o => o.cardData?.cardFunction)let functionListKey = [...(new Set(functionList.map(o => o?.functionType)))]let functionResult  = functionListKey.map((o) => {return functionList.find(fO => fO?.functionType == o)}) as CardFunctionInformation[];functionResult = functionResult.filter(o => !excludeDisplayedComponentTypes.includes(o?.functionType) )return functionResult ?? []} catch (error) {return {} as any}};}export const CardStatic = new CardStaticDataSet(returnStaticInformationOfTheCard())
5.工厂模式

描述:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使得一个类的实例化延迟到其子类。

在代码中的体现
CardStaticDataSet 类的构造函数本质上扮演了工厂角色,因为它接收参数并基于这些参数创建 CardStaticDataSet 实例。

constructor(params: Partial<CardStaticData>[]) {this.staticDataList = params;
}
6.策略模式

描述:定义一系列算法,把它们一个个封装起来,并且使它们可以互换。

在代码中的体现
returnComponentTypeAndName 方法中,通过 excludeDisplayedComponentTypes 策略来过滤组件类型。这些策略可以动态地改变,以影响方法的行为。

functionResult = functionResult.filter(o => !excludeDisplayedComponentTypes.includes(o?.functionType));
7.模板方法模式

CardStaticDataSet 类的各个方法(如 returnStaticDataOfTheSpecifiedComponentreturnTheStyleInformationOfTheSpecifiedComponentreturnComponentTypeAndName)中,都可以看到模板方法模式的应用。这些方法定义了一个算法的骨架,并处理一些通用的逻辑,如检查 key 是否存在于 staticDataList 中,并根据 key 查找对应的数据:

// 通用的检查和查找逻辑
if (this.staticDataList.map(o => o.moduleName).includes(key)) {let result = this.staticDataList.find(o => o.moduleName == key)?.cardData;// 进一步处理result
} else {throw new Error(key + '不存在对象中:' + JSON.stringify(this.staticDataList));
}

代码片段3

这是简单的路由

import { lazy } from 'react'; // 导入 React 的 lazy 函数,用于懒加载组件
// 路由配置对象
const routeConfig = {text: "首页", // 路由的显示名称selected: true, // 表示当前路由是否被选中darkMode: true, // 是否启用暗模式moduleId: 1291, // 路由对应的模块IDpath: "/index/index", // 路由的路径// 使用 React 的 lazy 函数实现懒加载组件lazyExoticComponent: lazy(() => import('../views/00_HomePage'))
};
// 示例:如何使用这个路由配置对象
// 假设我们有一个路由数组,包含多个路由配置
const routes = [routeConfig,// 其他路由配置...
];
// 在路由组件(例如 React Router 的 Route 组件)中使用这些路由配置
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Suspense } from 'react'; // 导入 Suspense 组件,用于处理懒加载的组件function App() {return (<Router><Suspense fallback={<div>Loading...</div>}><Switch>{routes.map((route, index) => (<Routekey={index}path={route.path}exactcomponent={route.lazyExoticComponent}/>))}</Switch></Suspense></Router>);
}export default App;
8.路由模式

描述: 将页面的不同状态映射到不同的URL路径上,使得用户可以直接通过URL来访问页面的不同状态。

在代码体现:

一般来说,路由模式包含以下几个关键部分:

  1. 路由表:定义URL路径与页面组件的映射关系。

  2. 路由器:负责监听URL路径的变化,根据路由表匹配对应的页面组件,并将其渲染到页面上。

  3. 历史记录管理器:负责管理浏览器的历史记录,以便用户可以使用浏览器的前进和后退按钮导航应用程序的不同状态。

代码片段4

// 原始对象 - 图片
class Image {constructor(url) {this.url = url;}// 加载图片load() {console.log(`Image loaded: ${this.url}`);}
}
// 代理对象 - 图片
class ProxyImage {constructor(url) {this.url = url;this.image = null; // 延迟加载}// 加载图片load() {if (!this.image) {this.image = new Image(this.url); // 延迟加载图片console.log(`Placeholder loaded for ${this.url}`);}this.image.load(); // 显示图片}
}
// 代码
const img1 = new ProxyImage('https://example.com/image1.jpg');
const img2 = new ProxyImage('https://example.com/image2.jpg');img1.load();
img1.load(); 
img2.load(); 
9.代理模式

描述: 它允许在不改变原始对象的情况下,通过引入一个代理对象来控制对原始对象的访问。代理对象充当原始对象的中介,客户端与代理对象交互,代理对象再将请求转发给原始对象。

在代码体现:

  1. ProxyImage 是代理对象,它控制对 Image 对象的访问。
  2. 当调用 img1.load() 时,代理对象 ProxyImage 首次创建并加载实际的图片对象 Image
  3. 当再次调用 img1.load() 时,代理对象 ProxyImage 不会重新创建 Image 对象,而是直接调用已经存在的 Image 对象的 load 方法。

最后

这是针对我目前项目用到的设计模式,总的一次总结,其它模式,也是在学习,跟试探,如何实际用到项目中去,对项目做一个总体的优化。 这些设计模式帮助我解决了在跨平台(如与 Android 交互)开发过程中遇到的诸多挑战,优化了代码的结构和可扩展性,避免了重复造轮子,同时保证了不同开发人员之间的代码规范一致性。

如:

  • 模块模式:通过立即执行函数表达式(IIFE)封装了JSBridge模块,确保了其内部逻辑的私密性,同时暴露了必要的接口进行外部访问。这样一来,代码的封装性得到了保证,减少了全局污染。

  • 单例模式:JSBridge作为全局单例存在,确保了桥接模块在项目中的唯一性,避免了多次初始化和重复资源的浪费。

  • 观察者模式和发布-订阅模式:通过事件监听和发布通知机制,使前端与 Android 之间的通信更加灵活,前端能够动态响应 Android 端的事件,极大地提高了系统的解耦性和扩展性。

  • 工厂模式:在数据管理中,通过工厂模式的应用动态地生成和管理不同类型的数据,使得扩展新功能时不需要对原有代码进行过多修改。

  • 策略模式:在组件的样式处理中,利用策略模式提供不同的处理方式,使得不同场景下的样式适配可以灵活切换。

  • 模板方法模式:通过统一的模板方法,简化了多个方法的重复逻辑,在保证通用性的同时,也使得开发过程更加高效。

  • 代理模式:通过代理模式,延迟加载图片资源,优化了性能,并且通过代理对象控制了资源的访问,避免了重复加载。

总结

通过这些模式的合理应用,我在提升代码质量的同时,也使得项目架构更加清晰、灵活且易于扩展。设计模式不仅仅是解决问题的工具,更是一个提升团队协作效率、增强代码可维护性和可复用性的有效方法。

未来,在面对更复杂的需求时,我还会不断探索并运用更多的设计模式,以进一步提升系统的质量和开发效率。设计模式的学习和应用是一个持续的过程,通过总结和反思,我可以不断优化代码结构,提高开发效率,从而更好地应对项目中的各类挑战。

参考:

浅谈前端出现率高的设计模式浅谈前端曝光率高的九大设计模式。分别从创建型:构造器模式、共产模式、单例模式;结构型:适配器模 - 掘金

前端设计模式大全(汇总详细版)1. 工厂模式 工厂模式(Factory Pattern):将对象的创建和使用分离,由工厂 - 掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu笔记

1.系统下载与虚拟机设置 系统下载https://cn.ubuntu.comhttps://releases.ubuntu.com 虚拟机设置: 桥接模式 在桥接模式下, 虚拟出来的操作系统就像是局域网中的一台独立的主机, 它可以访问网内任何一台机器主机网卡和虚拟网卡的IP地址处于同一个网段, 子网掩码、网关、DNS等…

开放世界目标检测 Grounding DINO

开放世界目标检测 Grounding DINO flyfish Grounding DINO 是一种开创性的开放集对象检测器&#xff0c;它通过结合基于Transformer的检测器DINO与基于文本描述的预训练技术&#xff0c;实现了可以根据人类输入&#xff08;如类别名称或指代表达&#xff09;检测任意对象的功…

【基础篇】1. JasperSoft Studio编辑器与报表属性介绍

编辑器介绍 Jaspersoft Studio有一个多选项卡编辑器&#xff0c;其中包括三个标签&#xff1a;设计&#xff0c;源代码和预览。 Design&#xff1a;报表设计页面&#xff0c;可以图形化拖拉组件设计报表&#xff0c;打开报表文件的主页面Source&#xff1a;源代码页码&#xff…

电子应用设计方案71:智能客厅窗帘系统设计

智能客厅窗帘系统设计 一、引言 智能客厅窗帘系统为用户提供了更加便捷、舒适和个性化的窗帘控制方式&#xff0c;提升了家居的智能化水平和生活品质。 二、系统概述 1. 系统目标 - 实现客厅窗帘的自动开合控制&#xff0c;可通过多种方式操作。 - 能够根据时间、光照强度和用…

免杀对抗—Behinder魔改流量特征去除

前言 在现实的攻防中&#xff0c;往往webshell要比主机后门要用得多&#xff0c;因为我们首先要突破的目标是网站嘛&#xff0c;而且waf也往往会更注重webshell的检测。webshell的免杀分为两个&#xff0c;一是静态查杀&#xff0c;二是流量查杀。静态查杀不用多说了&#xff…

高阶:基于Python paddleocr库 提取pdf 文档高亮显示的内容

预览 第1步&#xff1a;理解基本结构和导入必要的库 # 1. 首先导入需要的库 import os # 用于处理文件和路径 import cv2 # 用于图像处理 import numpy as np # 用于数值计算 from paddleocr import PaddleOCR # 用于文字识别 from pdf2image import convert_from_path #…

如何查看pad的console输出,以便我们更好的进行调试,查看并了解实际可能的问题。

1、以下是baidu AI回复&#xff1a; 2、说明&#xff1a; 1&#xff09;如果小伙伴们经常做android开发的话&#xff0c;这个不陌生&#xff0c;因为调试都是要开启这个开发者模式。并启用USB调试模式。 2&#xff09;需要连上USB线&#xff0c;有的时候会忘记&#xff0c;然…

GitHub 桌面版配置 |可视化界面进行上传到远程仓库 | gitLab 配置【把密码存在本地服务器】

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 桌面版安装包下载clone 仓库操作如下GitLab 配置不再重复输入账户和密码的两个方…

智慧社区电子商务系统:实现社区资源的数字化管理

2.1vue技术 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。 [5] 与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项…

【软件项目管理】-期末突击

区别常见的项目和活动 项目和活动的区别&#xff1a; 定义&#xff1a; 项目&#xff1a;为创造独特成果而进行的临时性工作。活动&#xff1a;日常运营中的重复性工作。 目标&#xff1a; 项目&#xff1a;实现特定成果&#xff0c;一次性。活动&#xff1a;维持日常运作&am…

【C++】ceil 和 floor 函数的实现与分析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;ceil 和 floor 函数的基础介绍1. ceil 函数定义与功能示例代码输出结果功能分析使用场景 2. floor 函数定义与功能示例代码输出结果功能分析使用场景 &#x1f4af;自行实现…

合合信息:探索视觉内容安全新前沿

2024年12月13日-15日&#xff0c;中国图象图形学学会在杭州召开。大会期间&#xff0c;来自合合信息的图像算法研发总监郭丰俊进行了主题为“视觉内容安全技术的前沿进展与应用”的演讲&#xff0c;介绍了视觉内容安全问题&#xff0c;并总结了现今的技术发展&#xff0c;对我很…

【JetPack】Navigation知识点总结

Navigation的主要元素&#xff1a; 1、Navigation Graph&#xff1a; 一种新的XML资源文件,包含应用程序所有的页面&#xff0c;以及页面间的关系。 <?xml version"1.0" encoding"utf-8"?> <navigation xmlns:android"http://schemas.a…

教师如何打造专属私密成绩查询系统?

期末的校园&#xff0c;被一种特殊的氛围所笼罩。老师们如同辛勤的工匠&#xff0c;精心打磨着每一个教学环节。复习阶段&#xff0c;他们在知识的宝库中精挑细选&#xff0c;把一学期的重点内容一一梳理&#xff0c;为学生们打造出系统的复习框架。课堂上&#xff0c;他们激情…

专栏二十三:Python读取和分析空间数据的经验杂谈

部分情况同样适合单细胞的分析结果 读取数据阶段 1.错误的library_id 包括sc和sq的两种读取方式&#xff0c;大同小异。 理论上有h5数据和spatial文件夹就可以读取成功&#xff0c;并且自动赋予和文件名一样的library_id&#xff0c;例如 slide sq.read.visium("/ho…

如何在谷歌浏览器中启用语音搜索

想象一下&#xff0c;你正在拥挤的地铁上&#xff0c;双手都拿着沉重的购物袋&#xff0c;突然你想搜索附近的咖啡馆。此时如果你能通过语音而不是打字来进行搜索&#xff0c;那将多么的便利&#xff01;在谷歌浏览器中&#xff0c;启用语音搜索功能就是这么简单而高效&#xf…

GCP GCA认证考试Case错题库1(JenciMart+Helicopter+EHR)

GCP GCA认证考试Case错题库1(JenciMartHelicopterEHR) 整理by Moshow郑锴https://zhengkai.blog.csdn.net/ JenciMart 在生产和开发资源之间进行管理职责分离的最小权限模型中&#xff0c;最佳实践是每个应用程序的每个阶段都有自己的项目。这种设置确保权限是细化的&#xf…

MySQL HA 方案 MMM、MHA、MGR、PXC 对比

MySQL高可用架构 MMM (Multi Master Replication Manager) 资源数量说明主DB2用于主备模式的主主复制从DB0~N台可以根据需要配置N台从服务器IP地址2n1N为MySQL服务器的数量监控用户1用户监控数据库状态的MySQL用户(replication)代理用户1用于MMM代理端改变read_only状态 故障…

java12.24日记

运算符&#xff1a; 算术运算符&#xff1a; 顾名思义进行算数运算的 多为&#xff1a;四则运算&#xff0c;加一个取余 &#xff0c;-&#xff0c;*&#xff0c;/以及 %&#xff08;取余&#xff09; 而外的&#xff1a;自增 以及自减--&#xff0c;对原数进行1或者-1 i…

SAP SD销售订单处理流程

本篇博文中的流程&#xff1a; 创建销售订单→依据销售订单创建交货单→依据销售订单开票 一、VA01创建销售订单 1、填入必填项&#xff0c;回车。可点击左上角的依照参考创建按钮。 依照参考创建可以参考以下6个。其中询价单、报价单、订单、合同和计划协议可以理解为特殊的…