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

news2024/12/23 18:57:04

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

前言

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

为什么要用设计模式?

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

  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)?.cardData
                return 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)?.cardData
                result = Object.assign({}, result)
                delete result?.cardStaticData
                return 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, // 路由对应的模块ID
    path: "/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) => (
                        <Route
                            key={index}
                            path={route.path}
                            exact
                            component={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.coloradmin.cn/o/2264377.html

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

相关文章

线性规划中的几种逻辑表达式

线性规划中的几种逻辑表达式 注意&#xff1a; 摘录字刘博士的《数学建模与数学规划》&#xff0c; 以便用时可查。 实际上Gurobi API 中自身放啊变的逻辑表达式函数&#xff0c;下面列出自定义的实现方式。 1 逻辑与 如果 x 1 1 x_1 1 x1​1, x 2 1 x_2 1 x2​1, 那…

JVM对象分配内存如何保证线程安全?

大家好&#xff0c;我是锋哥。今天分享关于【JVM对象分配内存如何保证线程安全&#xff1f;】面试题。希望对大家有帮助&#xff1b; JVM对象分配内存如何保证线程安全&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在JVM中&#xff0c;对象的内存分配…

Antd react上传图片格式限制

限制分辨率&#xff08;像素&#xff09; <a-upload :before-upload"beforeUpload">// 上传图片宽高比例限制const beforeUpload file > {return new Promise((resolve, reject) > {// // 图片类型限制// let isJpgOrPng file.type image/png || fil…

基于 iAP2 协议 的指令协议,用于对安防设备的 MCU 进行操作

协议设计目标 1. 安全性&#xff1a;通过 iAP2 协议与 MCU 设备进行安全通信。 2. 通用性&#xff1a;支持对安防设备的常见功能进行操作&#xff0c;如状态查询、设备控制、参数配置等。 3. 高效性&#xff1a;数据结构简洁清晰&#xff0c;易于解析和扩展。 4. 扩展性&#x…

Type-C单口便携屏LDR6021

随着科技的飞速发展&#xff0c;便携式电子产品在我们的日常生活中扮演着越来越重要的角色。在这一背景下&#xff0c;Type-C单口便携显示器作为一种新兴的显示设备&#xff0c;凭借其独特的优势迅速崭露头角&#xff0c;成为市场的新宠。本文将深入探讨Type-C单口便携显示器的…

Ubuntu 20.04 卸载和安装 MySQL8.0

卸载 首先&#xff0c;检查一下系统安装的软件包有哪些&#xff0c;使用dpkg -l | grep mysql命令&#xff1a; 为了将MySQL卸载干净&#xff0c;这些文件都需要被删除。 在Ubuntu20.04系统下&#xff0c;卸载干净MySQL8.0以确保下一次安装不会出错&#xff0c;可以按照以下…

NOTEBOOK_11 汽车电子设备分享(工作经验)

汽车电子设备分享 摘要 本文主要列出汽车电子应用的一些实验设备和生产设备&#xff0c;部分会给予一定推荐。目录 摘要一、通用工具&#xff1a;二、测量与测试仪器2.1测量仪器2.2无线通讯测量仪器2.3元器件测试仪2.4安规测试仪2.5电源供应器2.6电磁兼容测试设备2.7可靠性环境…

黑马Java面试教程_P8_并发编程

系列博客目录 文章目录 系列博客目录前言1.线程的基础知识1.1 线程和进程的区别&#xff1f;难2频3面试文稿 1.2 并行和并发有什么区别&#xff1f; 难1频1面试文稿 1.3 创建线程的四种方式 难2频4面试文稿 1.4 runnable 和 callable 有什么区别 难2频3面试文稿 1.5 线程的 run…

【活动邀请·深圳】深圳COC社区 深圳 AWS UG 2024 re:Invent re:Cap

re:Invent 是全球云计算领域的顶级盛会&#xff0c;每年都会吸引来自世界各地的技术领袖、创新者和实践者汇聚一堂&#xff0c;分享最新的技术成果和创新实践&#xff0c;深圳 UG 作为亚马逊云科技技术社区的重要组成部分&#xff0c;将借助 re:Invent 的东风&#xff0c;举办此…

一起学Git【第二节:创建版本库】

创建库 这个库相当于一个目录&#xff0c;目录中的文件都被Git管理&#xff0c;会记录每个文件的修改删除和添加工作&#xff0c;便于之后随时跟踪历史记录还原到之前的某一版本。如何创建库呢&#xff1f;有两种方式&#xff0c;本地创建库和云端克隆一个库。 1.本地创建库 …

本地部署webrtc应用怎么把http协议改成https协议?

环境&#xff1a; WSL2 Ubuntu22.04 webrtc视频聊天应用 问题描述&#xff1a; 本地部署webrtc应用怎么把http协议改成https协议&#xff1f; http协议在安卓手机浏览器上用不了麦克风本&#xff0c;来地应用webrtc 本来是http协议&#xff0c;在安卓手机上浏览器不支持使…

web实操8-cookie

会话技术 会话&#xff1a; 一次会话中包含多次请求和响应。 客户端浏览器访问服务器的资源&#xff0c;只要客户端或者服务器端不关闭&#xff0c;这始终在一次会话范围内&#xff0c;这一次会话范围内可以包含多次请求并且收到多次相应。 一次会话&#xff1a;浏览器第一…

vue2 - Day03 - (生命周期、组件、组件通信)

文章目录 一、生命周期1. 创建阶段2. 挂载阶段3. 更新阶段4. 销毁阶段5. 错误捕获总结 二、组件2.1 注册1. 全局注册 - 公共的组件。2. 局部注册总结 2.2 三大重要的组成部分1. 模板 (Template)主要功能&#xff1a;说明&#xff1a; 2. 脚本 (Script)主要功能&#xff1a;说明…

java日常工作开发高并发问题

前言 本篇文章将是以工作中经常遇到的问题&#xff0c;和面试中经常遇到的java问题进行描写。内容包括微服架构&#xff0c;java并发编程以及相应的中间件的高级知识。本文所有的问题都在描述多线程编程的高级知识。 一. 面试题 1.Sychronized和ReentrantLock有哪些不同点? …

【Python】【数据分析】深入探索 Python 数据可视化:Matplotlib 绘图库完整教程

目录 引言一、什么是 Matplotlib&#xff1f;1.1 Matplotlib 的安装1.2 Matplotlib 的基本功能 二、Matplotlib 的基础绘图2.1 绘制折线图2.2 绘制柱状图2.3 绘制散点图2.4 绘制饼图 三、高级功能与定制3.1 设置图表样式3.2 使用子图3.3 保存图表 四、Matplotlib 流程图4.1 Mer…

【代码随想录|动态规划背包问题】

一、背包问题分类 01背包&#xff1a;n种物品&#xff0c;每种物品只有一个 完全背包&#xff1a;n种物品&#xff0c;每种物品有无限个 多重背包&#xff1a;n种物品&#xff0c;每种物品的个数各不相同 二、01背包问题三道题 卡码网46题.携带研究材料&#xff08;二维背包…

第1章 命题逻辑

2024年12月22日 一稿 1.1 现代逻辑学的基本研究方法 1.2 命题及其表示法 1.2.1 命题的概念 定义1.1 命题是一个可以判断真假的陈述句。 1.2.2 联结词 非 与 或 蕴含 等价 1.3 命题公式与语句形式化 1.3.1 命题公式的定义 1.3.2 公式的层次 1.3.3 语句形式化 1…

Unity-Editor扩展GUI基本实现一个可拖拉放的格子列表

短短几百行代码,好吧,又是“参考”了国外的月亮 操作,还真地挺自然的。。。。。。国外的实现有点小牛 拖拉,增加+ 一个Element 鼠标左键长按,可以出提示 鼠标右键,清除Element, 有点小bug,不是很自然地完全清除, using System.Collections; using System.Collecti…

解决vscode ssh远程连接服务器一直卡在下载 vscode server问题

目录 方法1&#xff1a;使用科学上网 方法2&#xff1a;手动下载 方法3 在使用vscode使用ssh远程连接服务器时&#xff0c;一直卡在下载"vscode 服务器"阶段&#xff0c;但MobaXterm可以正常连接服务器&#xff0c;大概率是网络问题&#xff0c;解决方法如下: 方…

重拾设计模式--外观模式

文章目录 外观模式&#xff08;Facade Pattern&#xff09;概述定义 外观模式UML图作用 外观模式的结构C 代码示例1C代码示例2总结 外观模式&#xff08;Facade Pattern&#xff09;概述 定义 外观模式是一种结构型设计模式&#xff0c;它为子系统中的一组接口提供了一个统一…